diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index 8a226289c8a..bbc1ff88405 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -46,6 +46,7 @@ import org.eclipse.jetty.client.api.Destination; import org.eclipse.jetty.client.api.ProxyConfiguration; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; @@ -350,7 +351,7 @@ public class HttpClient extends ContainerLifeCycle newRequest.method(oldRequest.getMethod()) .version(oldRequest.getVersion()) .content(oldRequest.getContent()); - for (HttpFields.Field header : oldRequest.getHeaders()) + for (HttpField header : oldRequest.getHeaders()) { // We have a new URI, so skip the host header if present if (HttpHeader.HOST == header.getHeader()) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java new file mode 100644 index 00000000000..873a29b10d7 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java @@ -0,0 +1,217 @@ +// +// ======================================================================== +// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.http; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.Trie; + + +/* ------------------------------------------------------------ */ +/** A HTTP Field + */ +public class HttpField +{ + public final static Trie CACHE = new Trie<>(); + + static + { + CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE)); + CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.KEEP_ALIVE)); + CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip, deflate")); + CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-US,en;q=0.5")); + CACHE.put(new HttpField(HttpHeader.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")); + CACHE.put(new HttpField(HttpHeader.PRAGMA,"no-cache")); + CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"private, no-cache, no-cache=Set-Cookie, proxy-revalidate")); + CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"no-cache")); + CACHE.put(new HttpField(HttpHeader.CONTENT_LENGTH,"0")); + CACHE.put(new HttpField(HttpHeader.CONTENT_TYPE,"text/plain; charset=utf-8")); + CACHE.put(new HttpField(HttpHeader.CONTENT_TYPE,"application/x-www-form-urlencoded; charset=UTF-8")); + CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"gzip")); + CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"deflate")); + CACHE.put(new HttpField(HttpHeader.EXPIRES,"Fri, 01 Jan 1990 00:00:00 GMT")); + + + // Add headers with null values so HttpParser can avoid looking up name again for unknown values + CACHE.put(new HttpField(HttpHeader.CONNECTION,(String)null)); + CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,(String)null)); + CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,(String)null)); + CACHE.put(new HttpField(HttpHeader.ACCEPT,(String)null)); + CACHE.put(new HttpField(HttpHeader.PRAGMA,(String)null)); + CACHE.put(new HttpField(HttpHeader.CONTENT_TYPE,(String)null)); + CACHE.put(new HttpField(HttpHeader.CONTENT_LENGTH,(String)null)); + CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,(String)null)); + CACHE.put(new HttpField(HttpHeader.EXPIRES,(String)null)); + + // TODO add common user agents + } + + private final static byte[] __colon_space = new byte[] {':',' '}; + + private final HttpHeader _header; + private final String _name; + private final String _value; + + public HttpField(HttpHeader header, String name, String value) + { + _header = header; + _name = name; + _value = value; + } + + public HttpField(HttpHeader header, String value) + { + _header = header; + _name = _header.asString(); + _value = value; + } + + + public HttpField(HttpHeader header, HttpHeaderValue value) + { + _header = header; + _name = _header.asString(); + _value = value.asString(); + } + + public HttpField(String name, String value) + { + _header = HttpHeader.CACHE.get(name); + _name = name; + _value = value; + } + + public HttpHeader getHeader() + { + return _header; + } + + public String getName() + { + return _name; + } + + public String getValue() + { + return _value; + } + + public boolean contains(String value) + { + if (_value==null) + return false; + + if (value.equalsIgnoreCase(_value)) + return true; + + String[] split = _value.split("\\s*,\\s*"); + for (String s : split) + { + if (value.equalsIgnoreCase(s)) + return true; + } + + return false; + } + + + public int getIntValue() + { + return StringUtil.toInt(_value); + } + + public long getLongValue() + { + return StringUtil.toLong(_value); + } + + private byte[] toSanitisedName(String s) + { + byte[] bytes = s.getBytes(StringUtil.__ISO_8859_1_CHARSET); + for (int i=bytes.length;i-->0;) + { + switch(bytes[i]) + { + case '\r': + case '\n': + case ':' : + bytes[i]=(byte)'?'; + } + } + return bytes; + } + + private byte[] toSanitisedValue(String s) + { + byte[] bytes = s.getBytes(StringUtil.__ISO_8859_1_CHARSET); + for (int i=bytes.length;i-->0;) + { + switch(bytes[i]) + { + case '\r': + case '\n': + bytes[i]=(byte)'?'; + } + } + return bytes; + } + + public void putTo(ByteBuffer bufferInFillMode) + { + HttpHeader header = HttpHeader.CACHE.get(_name); + if (header!=null) + { + bufferInFillMode.put(header.getBytesColonSpace()); + + if (HttpHeaderValue.hasKnownValues(header)) + { + HttpHeaderValue value=HttpHeaderValue.CACHE.get(_value); + if (value!=null) + bufferInFillMode.put(value.toBuffer()); + else + bufferInFillMode.put(toSanitisedValue(_value)); + } + else + bufferInFillMode.put(toSanitisedValue(_value)); + } + else + { + bufferInFillMode.put(toSanitisedName(_name)); + bufferInFillMode.put(__colon_space); + bufferInFillMode.put(toSanitisedValue(_value)); + } + + BufferUtil.putCRLF(bufferInFillMode); + } + + public void putValueTo(ByteBuffer buffer) + { + buffer.put(toSanitisedValue(_value)); + } + + + @Override + public String toString() + { + String v=getValue(); + return getName() + ": " + (v==null?"":v.toString()); + } +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java index 94551ed56cd..e0613787363 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java @@ -29,11 +29,13 @@ import java.util.Date; import java.util.Enumeration; import java.util.GregorianCalendar; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Set; import java.util.StringTokenizer; import java.util.TimeZone; @@ -46,7 +48,6 @@ import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -// TODO Make this class inherit from oej.util.Fields /** * HTTP Fields. A collection of HTTP header and or Trailer fields. @@ -55,7 +56,7 @@ import org.eclipse.jetty.util.log.Logger; * single thread. * */ -public class HttpFields implements Iterable +public class HttpFields implements Iterable { private static final Logger LOG = Log.getLogger(HttpFields.class); public static final String __COOKIE_DELIM="\"\\\n\r\t\f\b%+ ;="; @@ -274,9 +275,7 @@ public class HttpFields implements Iterable public final static String __01Jan1970=formatDate(0); public final static ByteBuffer __01Jan1970_BUFFER=BufferUtil.toBuffer(__01Jan1970); public final static String __01Jan1970_COOKIE = formatCookieDate(0).trim(); - private final static byte[] __colon_space = new byte[] {':',' '}; - private final ArrayList _fields = new ArrayList<>(20); - private final StringMap _names = new StringMap<>(true); + private final ArrayList _fields = new ArrayList<>(20); /** * Constructor. @@ -291,11 +290,11 @@ public class HttpFields implements Iterable */ public Collection getFieldNamesCollection() { - final List list = new ArrayList<>(_fields.size()); - for (Field f : _fields) + final Set list = new HashSet<>(_fields.size()); + for (HttpField f : _fields) { if (f!=null) - list.add(f._name); + list.add(f.getName()); } return list; } @@ -306,21 +305,7 @@ public class HttpFields implements Iterable */ public Enumeration getFieldNames() { - final Enumeration buffers = Collections.enumeration(_names.keySet()); - return new Enumeration() - { - @Override - public String nextElement() - { - return buffers.nextElement().toString(); - } - - @Override - public boolean hasMoreElements() - { - return buffers.hasMoreElements(); - } - }; + return Collections.enumeration(getFieldNamesCollection()); } public int size() @@ -333,30 +318,70 @@ public class HttpFields implements Iterable * @return A Field value or null if the Field value has not been set * */ - public Field getField(int i) + public HttpField getField(int i) { return _fields.get(i); } @Override - public Iterator iterator() + public Iterator iterator() { return _fields.iterator(); } - public Field getField(HttpHeader header) + public HttpField getField(HttpHeader header) { - return _names.get(header.toString()); + for (int i=0;i<_fields.size();i++) + { + HttpField f=_fields.get(i); + if (f.getHeader()==header) + return f; + } + return null; } - public Field getField(String name) + public HttpField getField(String name) { - return _names.get(name); + for (int i=0;i<_fields.size();i++) + { + HttpField f=_fields.get(i); + if (f.getName().equalsIgnoreCase(name)) + return f; + } + return null; } + public boolean contains(HttpHeader header, String value) + { + for (int i=0;i<_fields.size();i++) + { + HttpField f=_fields.get(i); + if (f.getHeader()==header && f.contains(value)) + return true; + } + return false; + } + + public boolean contains(String name, String value) + { + for (int i=0;i<_fields.size();i++) + { + HttpField f=_fields.get(i); + if (f.getName().equalsIgnoreCase(name) && f.contains(value)) + return true; + } + return false; + } + public boolean containsKey(String name) { - return _names.containsKey(name); + for (int i=0;i<_fields.size();i++) + { + HttpField f=_fields.get(i); + if (f.getName().equalsIgnoreCase(name)) + return true; + } + return false; } public String getStringField(HttpHeader header) @@ -381,7 +406,7 @@ public class HttpFields implements Iterable */ public String getStringField(String name) { - Field field = getField(name); + HttpField field = getField(name); return field==null?null:field.getValue(); } @@ -394,17 +419,10 @@ public class HttpFields implements Iterable */ public Collection getValuesCollection(String name) { - Field field = getField(name); - if (field==null) - return null; - final List list = new ArrayList<>(); - - while(field!=null) - { - list.add(field.getValue()); - field=field._next; - } + for (HttpField f : _fields) + if (f.getName().equalsIgnoreCase(name)) + list.add(f.getValue()); return list; } @@ -414,34 +432,56 @@ public class HttpFields implements Iterable * @return Enumeration of the values * @param name the case-insensitive field name */ - public Enumeration getValues(String name) + public Enumeration getValues(final String name) { - final Field field = getField(name); - if (field == null) + for (int i=0;i<_fields.size();i++) { - List empty=Collections.emptyList(); - return Collections.enumeration(empty); + final HttpField f = _fields.get(i); + + if (f.getName().equalsIgnoreCase(name)) + { + final int first=i; + return new Enumeration() + { + HttpField field=f; + int i = first+1; + + @Override + public boolean hasMoreElements() + { + if (field==null) + { + while (i<_fields.size()) + { + field=_fields.get(i++); + if (field.getName().equalsIgnoreCase(name)) + return true; + } + field=null; + return false; + } + return true; + } + + @Override + public String nextElement() throws NoSuchElementException + { + if (hasMoreElements()) + { + String value=field.getValue(); + field=null; + return value; + } + throw new NoSuchElementException(); + } + + }; + } } - return new Enumeration() - { - Field f = field; + List empty=Collections.emptyList(); + return Collections.enumeration(empty); - @Override - public boolean hasMoreElements() - { - return f != null; - } - - @Override - public String nextElement() throws NoSuchElementException - { - if (f == null) throw new NoSuchElementException(); - Field n = f; - f = f._next; - return n.getValue(); - } - }; } /** @@ -459,7 +499,7 @@ public class HttpFields implements Iterable if (e == null) return null; return new Enumeration() - { + { QuotedStringTokenizer tok = null; @Override @@ -484,7 +524,7 @@ public class HttpFields implements Iterable if (next != null) next = next.trim(); return next; } - }; + }; } @@ -501,9 +541,8 @@ public class HttpFields implements Iterable return; // new value; - Field field = new Field(name, value); + HttpField field = new HttpField(name, value); _fields.add(field); - _names.put(name, field); } public void put(HttpHeader header, HttpHeaderValue value) @@ -519,14 +558,13 @@ public class HttpFields implements Iterable */ public void put(HttpHeader header, String value) { - remove(header.toString()); + remove(header); if (value == null) return; // new value; - Field field = new Field(header, value); + HttpField field = new HttpField(header, value); _fields.add(field); - _names.put(header.toString(), field); } /** @@ -557,23 +595,8 @@ public class HttpFields implements Iterable if (value == null) return; - Field field = _names.get(name); - Field last = null; - while (field != null) - { - last = field; - field = field._next; - } - - // create the field - field = new Field(name, value); + HttpField field = new HttpField(name, value); _fields.add(field); - - // look for chain to add too - if (last != null) - last._next = field; - else - _names.put(name, field); } public void add(HttpHeader header, HttpHeaderValue value) throws IllegalArgumentException @@ -587,30 +610,14 @@ public class HttpFields implements Iterable * * @param header the header * @param value the value of the field. - * @exception IllegalArgumentException If the name is a single valued field and already has a - * value. + * @exception IllegalArgumentException */ public void add(HttpHeader header, String value) throws IllegalArgumentException { if (value == null) throw new IllegalArgumentException("null value"); - Field field = _names.get(header.toString()); - Field last = null; - while (field != null) - { - last = field; - field = field._next; - } - - // create the field - field = new Field(header, value); + HttpField field = new HttpField(header, value); _fields.add(field); - - // look for chain to add too - if (last != null) - last._next = field; - else - _names.put(header.toString(), field); } /** @@ -620,7 +627,13 @@ public class HttpFields implements Iterable */ public void remove(HttpHeader name) { - remove(name.toString()); + Iterator i=_fields.iterator(); + while (i.hasNext()) + { + HttpField f=i.next(); + if (f.getHeader()==name) + i.remove(); + } } /** @@ -630,11 +643,12 @@ public class HttpFields implements Iterable */ public void remove(String name) { - Field field = _names.remove(name); - while (field != null) + Iterator i=_fields.iterator(); + while (i.hasNext()) { - _fields.remove(field); - field = field._next; + HttpField f=i.next(); + if (f.getName().equalsIgnoreCase(name)) + i.remove(); } } @@ -647,7 +661,7 @@ public class HttpFields implements Iterable */ public long getLongField(String name) throws NumberFormatException { - Field field = getField(name); + HttpField field = getField(name); return field==null?-1L:field.getLongValue(); } @@ -659,11 +673,11 @@ public class HttpFields implements Iterable */ public long getDateField(String name) { - Field field = getField(name); + HttpField field = getField(name); if (field == null) return -1; - String val = valueParameters(field._value, null); + String val = valueParameters(field.getValue(), null); if (val == null) return -1; @@ -840,29 +854,27 @@ public class HttpFields implements Iterable name_value_params = buf.toString(); // remove existing set-cookie of same name - Field field = getField(HttpHeader.SET_COOKIE); - Field last=null; - while (field!=null) - { - String val = (field._value == null ? null : field._value.toString()); - if (val!=null && val.startsWith(start)) - { - //existing cookie has same name, does it also match domain and path? - if (((!hasDomain && !val.contains("Domain")) || (hasDomain && val.contains("Domain="+domain))) && - ((!hasPath && !val.contains("Path")) || (hasPath && val.contains("Path="+path)))) - { - _fields.remove(field); - if (last==null) - _names.put(HttpHeader.SET_COOKIE.toString(),field._next); - else - last._next=field._next; - break; - } - } - last=field; - field=field._next; - } + Iterator i=_fields.iterator(); + while (i.hasNext()) + { + HttpField field=i.next(); + if (field.getHeader()==HttpHeader.SET_COOKIE) + { + String val = (field.getValue() == null ? null : field.getValue().toString()); + if (val!=null && val.startsWith(start)) + { + //existing cookie has same name, does it also match domain and path? + if (((!hasDomain && !val.contains("Domain")) || (hasDomain && val.contains("Domain="+domain))) && + ((!hasPath && !val.contains("Path")) || (hasPath && val.contains("Path="+path)))) + { + i.remove(); + } + } + + } + } + add(HttpHeader.SET_COOKIE.toString(), name_value_params); // Expire responses with set-cookie headers so they do not get cached. @@ -871,7 +883,7 @@ public class HttpFields implements Iterable public void putTo(ByteBuffer bufferInFillMode) throws IOException { - for (Field field : _fields) + for (HttpField field : _fields) { if (field != null) field.putTo(bufferInFillMode); @@ -886,7 +898,7 @@ public class HttpFields implements Iterable try { StringBuilder buffer = new StringBuilder(); - for (Field field : _fields) + for (HttpField field : _fields) { if (field != null) { @@ -914,7 +926,6 @@ public class HttpFields implements Iterable public void clear() { _fields.clear(); - _names.clear(); } /** @@ -1088,151 +1099,5 @@ public class HttpFields implements Iterable return vl; } - public static final class Field - { - private final HttpHeader _header; - private final String _name; - private final String _value; - private Field _next; - private Field(HttpHeader header, String value) - { - _header = header; - _name = header.toString(); - _value = value; - _next = null; - } - - private Field(String name, String value) - { - _header = HttpHeader.CACHE.get(name); - _name = _header==null?name:_header.toString(); - _value = value; - _next = null; - } - - private byte[] toSanitisedName(String s) - { - byte[] bytes = s.getBytes(StringUtil.__ISO_8859_1_CHARSET); - for (int i=bytes.length;i-->0;) - { - switch(bytes[i]) - { - case '\r': - case '\n': - case ':' : - bytes[i]=(byte)'?'; - } - } - return bytes; - } - - private byte[] toSanitisedValue(String s) - { - byte[] bytes = s.getBytes(StringUtil.__ISO_8859_1_CHARSET); - for (int i=bytes.length;i-->0;) - { - switch(bytes[i]) - { - case '\r': - case '\n': - bytes[i]=(byte)'?'; - } - } - return bytes; - } - - public void putTo(ByteBuffer bufferInFillMode) - { - HttpHeader header = HttpHeader.CACHE.get(_name); - if (header!=null) - { - bufferInFillMode.put(header.getBytesColonSpace()); - - if (HttpHeaderValue.hasKnownValues(header)) - { - HttpHeaderValue value=HttpHeaderValue.CACHE.get(_value); - if (value!=null) - bufferInFillMode.put(value.toBuffer()); - else - bufferInFillMode.put(toSanitisedValue(_value)); - } - else - bufferInFillMode.put(toSanitisedValue(_value)); - } - else - { - bufferInFillMode.put(toSanitisedName(_name)); - bufferInFillMode.put(__colon_space); - bufferInFillMode.put(toSanitisedValue(_value)); - } - - BufferUtil.putCRLF(bufferInFillMode); - } - - public void putValueTo(ByteBuffer buffer) - { - buffer.put(toSanitisedValue(_value)); - } - - public HttpHeader getHeader() - { - return _header; - } - - public String getName() - { - return _name; - } - - public String getValue() - { - return _value; - } - - public int getIntValue() - { - return StringUtil.toInt(_value); - } - - public long getLongValue() - { - return StringUtil.toLong(_value); - } - - @Override - public String toString() - { - return ("[" + getName() + "=" + _value + (_next == null ? "" : "->") + "]"); - } - - public boolean contains(String value) - { - if (_value==null) - return false; - - if (value.equalsIgnoreCase(_value)) - return true; - - String[] split = _value.split("\\s*,\\s*"); - for (String s : split) - { - if (value.equalsIgnoreCase(s)) - return true; - } - - if (_next!=null) - return _next.contains(value); - - return false; - } - } - - public boolean contains(HttpHeader header, String value) - { - Field field = getField(header); - if (field==null) - return false; - return field.contains(value); - } } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java index 2aa291d4d86..6650676e7ef 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java @@ -542,7 +542,7 @@ public class HttpGenerator // default field values boolean has_server = false; - HttpFields.Field transfer_encoding=null; + HttpField transfer_encoding=null; boolean keep_alive=false; boolean close=false; boolean content_type=false; @@ -551,11 +551,11 @@ public class HttpGenerator // Generate fields if (_info.getHttpFields() != null) { - for (HttpFields.Field field : _info.getHttpFields()) + for (HttpField field : _info.getHttpFields()) { - HttpHeader name = field.getHeader(); + HttpHeader h = field.getHeader(); - switch (name==null?HttpHeader.UNKNOWN:name) + switch (h==null?HttpHeader.UNKNOWN:h) { case CONTENT_LENGTH: // handle specially below @@ -666,11 +666,11 @@ public class HttpGenerator } default: - if (name==null) + if (h==null) field.putTo(header); else { - header.put(name.getBytesColonSpace()); + header.put(h.getBytesColonSpace()); field.putValueTo(header); header.put(CRLF); } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java index ae93190e30c..61f457eb3be 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java @@ -19,11 +19,9 @@ package org.eclipse.jetty.http; import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; -import org.eclipse.jetty.util.StringMap; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.Trie; public enum HttpHeader @@ -114,7 +112,7 @@ public enum HttpHeader /* ------------------------------------------------------------ */ - public final static StringMap CACHE= new StringMap(true); + public final static Trie CACHE= new Trie(); static { for (HttpHeader header : HttpHeader.values()) @@ -122,71 +120,6 @@ public enum HttpHeader CACHE.put(header.toString(),header); } - /* ------------------------------------------------------------ */ - private final static HttpHeader[] __hashed= new HttpHeader[4096]; - private final static int __maxHashed; - static - { - // This hash function has been picked to have no collisions for - // the known header values. This allows a very quick lookup. - int max=0; - Map hashes=new HashMap<>(); - for (HttpHeader header : HttpHeader.values()) - { - String s=header.asString(); - max=Math.max(max,s.length()); - int h=0; - for (char c:s.toCharArray()) - h = 31*h + ((c>='a')?(c-'a'+'A'):c); - int hash=h%__hashed.length; - if (hash<0)hash=-hash; - if (hashes.containsKey(hash)) - { - // This should not happen with known headers. - System.err.println("Duplicate hash "+header+" "+hashes.get(hash)); - System.exit(1); - } - hashes.put(hash,header); - __hashed[hash]=header; - } - __maxHashed=max; - } - - public static HttpHeader lookAheadGet(byte[] bytes, int position, int limit) - { - int h=0; - byte b=0; - limit=Math.min(position+__maxHashed,limit); - for (int i=position;i='a')?(b-'a'+'A'):b); - } - if (b!=':'&&b!=' ') - return null; - - int hash=h%__hashed.length; - if (hash<0)hash=-hash; - HttpHeader header=__hashed[hash]; - - if (header!=null) - { - String s=header.asString(); - for (int i=s.length();i-->0;) - { - b=bytes[position+i]; - char c=s.charAt(i); - if (c!=b && Character.toUpperCase(c)!=(b>='a'?(b-'a'+'A'):b)) - return null; - } - } - - return header; - } - - private final String _string; private final byte[] _bytes; private final byte[] _bytesColonSpace; diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java index f86c9f6d062..6898ba96986 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java @@ -22,7 +22,7 @@ import java.nio.ByteBuffer; import java.util.EnumSet; import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.StringMap; +import org.eclipse.jetty.util.Trie; /** @@ -44,7 +44,7 @@ public enum HttpHeaderValue UNKNOWN("::UNKNOWN::"); /* ------------------------------------------------------------ */ - public final static StringMap CACHE= new StringMap(true); + public final static Trie CACHE= new Trie(); static { for (HttpHeaderValue value : HttpHeaderValue.values()) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java index 2fa71948648..a13e8b1f035 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java @@ -20,8 +20,8 @@ package org.eclipse.jetty.http; import java.nio.ByteBuffer; -import org.eclipse.jetty.util.StringMap; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.Trie; /* ------------------------------------------------------------------------------- */ @@ -109,11 +109,11 @@ public enum HttpMethod { if (buffer.hasArray()) return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit()); - return null; + return CACHE.getBest(buffer,0,buffer.remaining()); } /* ------------------------------------------------------------ */ - public final static StringMap CACHE= new StringMap(true); + public final static Trie CACHE= new Trie(); static { for (HttpMethod method : HttpMethod.values()) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index c9ce1940c93..22d8ec5c878 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -508,6 +508,86 @@ public class HttpParser return return_from_parse; } + private boolean handleKnownHeaders(ByteBuffer buffer) + { + switch (_header) + { + case CONTENT_LENGTH: + if (_endOfContent != EndOfContent.CHUNKED_CONTENT) + { + try + { + _contentLength=Long.parseLong(_valueString); + } + catch(NumberFormatException e) + { + LOG.ignore(e); + badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Bad Content-Length"); + return true; + } + if (_contentLength <= 0) + _endOfContent=EndOfContent.NO_CONTENT; + else + _endOfContent=EndOfContent.CONTENT_LENGTH; + } + break; + + case TRANSFER_ENCODING: + if (_value==HttpHeaderValue.CHUNKED) + _endOfContent=EndOfContent.CHUNKED_CONTENT; + else + { + if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString())) + _endOfContent=EndOfContent.CHUNKED_CONTENT; + else if (_valueString.indexOf(HttpHeaderValue.CHUNKED.toString()) >= 0) + { + badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Bad chunking"); + return true; + } + } + break; + + case HOST: + _host=true; + String host=_valueString; + int port=0; + if (host==null || host.length()==0) + { + badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Bad Host header"); + return true; + } + + loop: for (int i = host.length(); i-- > 0;) + { + char c2 = (char)(0xff & host.charAt(i)); + switch (c2) + { + case ']': + break loop; + + case ':': + try + { + port = StringUtil.toInt(host.substring(i+1)); + } + catch (NumberFormatException e) + { + LOG.debug(e); + badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Bad Host header"); + return true; + } + host = host.substring(0,i); + break loop; + } + } + if (_requestHandler!=null) + _requestHandler.parsedHostHeader(host,port); + } + + return false; + } + + /* ------------------------------------------------------------------------------- */ /* * Parse the message headers and return true if the handler has signaled for a return @@ -545,8 +625,14 @@ public class HttpParser case HttpTokens.TAB: { // header value without name - continuation? - _length=-1; _string.setLength(0); + if (_valueString!=null) + { + _string.append(_valueString); + _string.append(' '); + } + _length=_string.length(); + _valueString=null; setState(State.HEADER_VALUE); break; } @@ -557,82 +643,8 @@ public class HttpParser if (_headerString!=null || _valueString!=null) { // Handle known headers - if (_header!=null) - { - switch (_header) - { - case CONTENT_LENGTH: - if (_endOfContent != EndOfContent.CHUNKED_CONTENT) - { - try - { - _contentLength=Long.parseLong(_valueString); - } - catch(NumberFormatException e) - { - LOG.ignore(e); - badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Bad Content-Length"); - return true; - } - if (_contentLength <= 0) - _endOfContent=EndOfContent.NO_CONTENT; - else - _endOfContent=EndOfContent.CONTENT_LENGTH; - } - break; - - case TRANSFER_ENCODING: - if (_value==HttpHeaderValue.CHUNKED) - _endOfContent=EndOfContent.CHUNKED_CONTENT; - else - { - if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString())) - _endOfContent=EndOfContent.CHUNKED_CONTENT; - else if (_valueString.indexOf(HttpHeaderValue.CHUNKED.toString()) >= 0) - { - badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Bad chunking"); - return true; - } - } - break; - - case HOST: - _host=true; - String host=_valueString; - int port=0; - if (host==null || host.length()==0) - { - badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Bad Host header"); - return true; - } - - loop: for (int i = host.length(); i-- > 0;) - { - char c2 = (char)(0xff & host.charAt(i)); - switch (c2) - { - case ']': - break loop; - - case ':': - try - { - port = StringUtil.toInt(host.substring(i+1)); - } - catch (NumberFormatException e) - { - LOG.debug(e); - badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Bad Host header"); - return true; - } - host = host.substring(0,i); - break loop; - } - } - if (_requestHandler!=null) - _requestHandler.parsedHostHeader(host,port); - } - } + if (_header!=null && handleKnownHeaders(buffer)) + return true; return_from_parse|=_handler.parsedHeader(_header, _headerString, _valueString); } @@ -704,14 +716,36 @@ public class HttpParser { if (buffer.remaining()>6 && buffer.hasArray()) { - // Try a look ahead for the known headers. - _header=HttpHeader.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit()); + // Try a look ahead for the known header name and value. + HttpField field=HttpField.CACHE.getBest(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.remaining()+1); + if (field!=null) + { + _header=field.getHeader(); + _headerString=field.getName(); + _valueString=field.getValue(); + if (_valueString==null) + { + setState(State.HEADER_VALUE); + buffer.position(buffer.position()+_headerString.length()+1); + _string.setLength(0); + _length=0; + } + else + { + setState(State.HEADER_IN_VALUE); + buffer.position(buffer.position()+_headerString.length()+_valueString.length()+1); + } + break; + } + // Try a look ahead for the known header name. + _header=HttpHeader.CACHE.getBest(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.remaining()+1); if (_header!=null) { _headerString=_header.asString(); - buffer.position(buffer.position()+_headerString.length()); - setState(buffer.get(buffer.position()-1)==':'?State.HEADER_VALUE:State.HEADER_NAME); + _string.setLength(0); + setState(State.HEADER_IN_NAME); + buffer.position(buffer.position()+_headerString.length()-1); break; } } @@ -733,8 +767,11 @@ public class HttpParser case HttpTokens.CARRIAGE_RETURN: case HttpTokens.LINE_FEED: consumeCRLF(ch,buffer); - _headerString=takeLengthString(); - _header=HttpHeader.CACHE.get(_headerString); + if (_headerString==null) + { + _headerString=takeLengthString(); + _header=HttpHeader.CACHE.get(_headerString); + } setState(State.HEADER); break; @@ -753,15 +790,6 @@ public class HttpParser break; default: { - if (_header!=null) - { - _string.setLength(0); - _string.append(_header.asString()); - _string.append(' '); - _length=_string.length(); - _header=null; - _headerString=null; - } _string.append((char)ch); _length=_string.length(); setState(State.HEADER_IN_NAME); @@ -793,10 +821,26 @@ public class HttpParser break; case HttpTokens.SPACE: case HttpTokens.TAB: + if (_header!=null) + { + _string.setLength(0); + _string.append(_header.asString()); + _length=_string.length(); + _header=null; + _headerString=null; + } setState(State.HEADER_NAME); _string.append((char)ch); break; default: + if (_header!=null) + { + _string.setLength(0); + _string.append(_header.asString()); + _length=_string.length(); + _header=null; + _headerString=null; + } _string.append((char)ch); _length++; } @@ -849,13 +893,7 @@ public class HttpParser consumeCRLF(ch,buffer); if (_length > 0) { - if (_valueString!=null) - { - // multi line value! - _value=null; - _valueString+=" "+takeString(); - } - else if (HttpHeaderValue.hasKnownValues(_header)) + if (HttpHeaderValue.hasKnownValues(_header)) { _valueString=takeString(); _value=HttpHeaderValue.CACHE.get(_valueString); @@ -871,10 +909,24 @@ public class HttpParser break; case HttpTokens.SPACE: case HttpTokens.TAB: + if (_valueString!=null) + { + _string.setLength(0); + _string.append(_valueString); + _length=_valueString.length(); + _valueString=null; + } _string.append((char)ch); setState(State.HEADER_VALUE); break; default: + if (_valueString!=null) + { + _string.setLength(0); + _string.append(_valueString); + _length=_valueString.length(); + _valueString=null; + } _string.append((char)ch); _length++; } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpScheme.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpScheme.java index 0253a59ea6e..e50bafaab93 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpScheme.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpScheme.java @@ -21,7 +21,7 @@ package org.eclipse.jetty.http; import java.nio.ByteBuffer; import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.StringMap; +import org.eclipse.jetty.util.Trie; /* ------------------------------------------------------------------------------- */ /** @@ -34,7 +34,7 @@ public enum HttpScheme WSS("wss"); /* ------------------------------------------------------------ */ - public final static StringMap CACHE= new StringMap(true); + public final static Trie CACHE= new Trie(); static { for (HttpScheme version : HttpScheme.values()) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java index f6c9319a9d1..4ea39f6be59 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java @@ -20,8 +20,8 @@ package org.eclipse.jetty.http; import java.nio.ByteBuffer; -import org.eclipse.jetty.util.StringMap; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.Trie; /* ------------------------------------------------------------------------------- */ @@ -33,7 +33,7 @@ public enum HttpVersion HTTP_2_0("HTTP/2.0",20); /* ------------------------------------------------------------ */ - public final static StringMap CACHE= new StringMap(true); + public final static Trie CACHE= new Trie(); static { for (HttpVersion version : HttpVersion.values()) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java b/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java index 17b6056a54d..603f026c67c 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java @@ -28,8 +28,8 @@ import java.util.MissingResourceException; import java.util.ResourceBundle; import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.StringMap; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.Trie; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -109,8 +109,8 @@ public class MimeTypes /* ------------------------------------------------------------ */ private static final Logger LOG = Log.getLogger(MimeTypes.class); - public final static StringMap CACHE= new StringMap(true); - private final static StringMap TYPES= new StringMap(true); + public final static Trie CACHE= new Trie(); + private final static Trie TYPES= new Trie(); private final static Map __dftMimeMap = new HashMap(); private final static Map __encodings = new HashMap(); diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java index 83842aa2805..48b41e43994 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java @@ -514,8 +514,8 @@ public class HttpFieldsTest for (int i=0;i<7;i++) { - assertFalse(""+i,header.getField(""+i).contains("xyz")); - assertEquals(""+i,i>=4,header.getField(""+i).contains("def")); + assertFalse(""+i,header.contains(""+i,"xyz")); + assertEquals(""+i,i>=4,header.contains(""+i,"def")); } } } diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java index 2f3dca8494c..2abf6a5e6ba 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.nio.ByteBuffer; +import java.util.Arrays; import org.eclipse.jetty.http.HttpParser.State; import org.eclipse.jetty.util.BufferUtil; @@ -153,12 +154,15 @@ public class HttpParserTest "Host: localhost\015\012" + "Header1: value1\015\012" + "Header 2 : value 2a \015\012" + - " value 2b \015\012" + + " value 2b \015\012" + "Header3: \015\012" + "Header4 \015\012" + " value4\015\012" + "Server5 : notServer\015\012" + "Host Header: notHost\015\012" + + "Connection: close\015\012" + + "Accept-Encoding: gzip, deflated\015\012" + + "Accept: unknown\015\012" + "\015\012"); Handler handler = new Handler(); HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); @@ -181,7 +185,13 @@ public class HttpParserTest assertEquals("notServer", _val[5]); assertEquals("Host Header", _hdr[6]); assertEquals("notHost", _val[6]); - assertEquals(6, _h); + assertEquals("Connection", _hdr[7]); + assertEquals("close", _val[7]); + assertEquals("Accept-Encoding", _hdr[8]); + assertEquals("gzip, deflated", _val[8]); + assertEquals("Accept", _hdr[9]); + assertEquals("unknown", _val[9]); + assertEquals(9, _h); } @Test @@ -736,8 +746,8 @@ public class HttpParserTest { request=true; _h= -1; - _hdr= new String[9]; - _val= new String[9]; + _hdr= new String[10]; + _val= new String[10]; _methodOrVersion= method; _uriOrStatus= uri; _versionOrReason= version==null?null:version.asString(); diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java index 795cd6b8c4a..5e44c146a74 100644 --- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java @@ -44,6 +44,7 @@ import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.util.InputStreamContentProvider; import org.eclipse.jetty.client.util.TimedResponseListener; +import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpVersion; @@ -464,7 +465,7 @@ public class ProxyServlet extends HttpServlet protected void onResponseHeaders(HttpServletRequest request, HttpServletResponse response, Response proxyResponse) { - for (HttpFields.Field field : proxyResponse.getHeaders()) + for (HttpField field : proxyResponse.getHeaders()) { String headerName = field.getName(); String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH); diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java index e701dad9255..55f677b358a 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -479,7 +480,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr Map mappings = _constraintMap.get(mapping.getPathSpec()); if (mappings == null) { - mappings = new StringMap<>(); + mappings = new HashMap(); _constraintMap.put(mapping.getPathSpec(),mappings); } RoleInfo allMethodsRoleInfo = mappings.get(ALL_METHODS); diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java index b426b393e12..7895b0f1fdc 100644 --- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java +++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java @@ -25,6 +25,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpGenerator; import org.eclipse.jetty.http.HttpStatus; @@ -128,7 +129,7 @@ public class HttpTransportOverSPDY implements HttpTransport { for (int i = 0; i < fields.size(); ++i) { - HttpFields.Field field = fields.getField(i); + HttpField field = fields.getField(i); String name = field.getName(); String value = field.getValue(); headers.put(name, value); diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Trie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Trie.java new file mode 100644 index 00000000000..368be03ef25 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Trie.java @@ -0,0 +1,334 @@ +// +// ======================================================================== +// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.util; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Trie +{ + private static final int[] __lookup = + { // 0 1 2 3 4 5 6 7 8 9 A B C D E F + /*0*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*1*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 30, + /*2*/31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, 27, -1, -1, + /*3*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 28, 29, -1, -1, -1, -1, + /*4*/-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + /*5*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + /*6*/-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + /*7*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + }; + private static final int INDEX = 32; + private final Trie[] _nextIndex; + private final List> _nextOther=new ArrayList<>(); + private final char _c; + private String _key; + private V _value; + + public Trie() + { + _nextIndex = new Trie[INDEX]; + _c=0; + } + + private Trie(char c) + { + _nextIndex = new Trie[INDEX]; + this._c=c; + } + + public V put(V v) + { + return put(v.toString(),v); + } + + public V put(String s, V v) + { + Trie t = this; + int k; + int limit = s.length(); + for(k=0; k < limit; k++) + { + char c=s.charAt(k); + + int index=c>=0&&c<0x7f?__lookup[c]:-1; + if (index>=0) + { + if (t._nextIndex[index] == null) + t._nextIndex[index] = new Trie(c); + t = t._nextIndex[index]; + } + else + { + Trie n=null; + for (int i=t._nextOther.size();i-->0;) + { + n=t._nextOther.get(i); + if (n._c==c) + break; + n=null; + } + if (n==null) + { + n=new Trie(c); + t._nextOther.add(n); + } + t=n; + } + } + t._key=v==null?null:s; + V old=t._value; + t._value = v; + return old; + } + + public V remove(String s) + { + V o=get(s); + put(s,null); + return o; + } + + public V get(String s) + { + Trie t = this; + int len = s.length(); + for(int i=0; i < len; i++) + { + char c=s.charAt(i); + int index=c>=0&&c<0x7f?__lookup[c]:-1; + if (index>=0) + { + if (t._nextIndex[index] == null) + return null; + t = t._nextIndex[index]; + } + else + { + Trie n=null; + for (int j=t._nextOther.size();j-->0;) + { + n=t._nextOther.get(j); + if (n._c==c) + break; + n=null; + } + if (n==null) + return null; + t=n; + } + } + return t._value; + } + + public V get(ByteBuffer b,int offset,int len) + { + Trie t = this; + for(int i=0; i < len; i++) + { + byte c=b.get(offset+i); + int index=c>=0&&c<0x7f?__lookup[c]:-1; + if (index>=0) + { + if (t._nextIndex[index] == null) + return null; + t = t._nextIndex[index]; + } + else + { + Trie n=null; + for (int j=t._nextOther.size();j-->0;) + { + n=t._nextOther.get(j); + if (n._c==c) + break; + n=null; + } + if (n==null) + return null; + t=n; + } + } + return t._value; + } + + public V getBest(byte[] b,int offset,int len) + { + Trie t = this; + for(int i=0; i < len; i++) + { + byte c=b[offset+i]; + int index=c>=0&&c<0x7f?__lookup[c]:-1; + if (index>=0) + { + if (t._nextIndex[index] == null) + return null; + t = t._nextIndex[index]; + } + else + { + Trie n=null; + for (int j=t._nextOther.size();j-->0;) + { + n=t._nextOther.get(j); + if (n._c==c) + break; + n=null; + } + if (n==null) + return null; + t=n; + } + + // Is the next Trie is a match + if (t._key!=null) + { + // Recurse so we can remember this possibility + V best=t.getBest(b,offset+i+1,len-i-1); + if (best!=null) + return best; + return t._value; + } + } + return t._value; + } + + public V getBest(ByteBuffer b,int offset,int len) + { + if (b.hasArray()) + return getBest(b.array(),b.arrayOffset()+b.position()+offset,len); + return doGetBest(b,offset,len); + } + + private V doGetBest(ByteBuffer b,int offset,int len) + { + Trie t = this; + for(int i=0; i < len; i++) + { + byte c=b.get(offset+i); + int index=c>=0&&c<0x7f?__lookup[c]:-1; + if (index>=0) + { + if (t._nextIndex[index] == null) + return null; + t = t._nextIndex[index]; + } + else + { + Trie n=null; + for (int j=t._nextOther.size();j-->0;) + { + n=t._nextOther.get(j); + if (n._c==c) + break; + n=null; + } + if (n==null) + return null; + t=n; + } + + // Is the next Trie is a match + if (t._key!=null) + { + // Recurse so we can remember this possibility + V best=t.getBest(b,offset+i+1,len-i-1); + if (best!=null) + return best; + return t._value; + } + } + return t._value; + } + + + + + @Override + public String toString() + { + StringBuilder buf = new StringBuilder(); + toString(buf,this); + + if (buf.length()==0) + return "{}"; + + buf.setCharAt(0,'{'); + buf.append('}'); + return buf.toString(); + } + + + private static void toString(Appendable out, Trie t) + { + if (t != null) + { + if (t._value!=null) + { + try + { + out.append(','); + out.append(t._key); + out.append('='); + out.append(t._value.toString()); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + for(int i=0; i < INDEX; i++) + { + if (t._nextIndex[i] != null) + toString(out,t._nextIndex[i]); + } + for (int i=t._nextOther.size();i-->0;) + toString(out,t._nextOther.get(i)); + } + } + + public Set keySet() + { + Set keys = new HashSet<>(); + keySet(keys,this); + return keys; + } + + private static void keySet(Set set, Trie t) + { + if (t != null) + { + if (t._key!=null) + set.add(t._key); + + for(int i=0; i < INDEX; i++) + { + if (t._nextIndex[i] != null) + keySet(set,t._nextIndex[i]); + } + for (int i=t._nextOther.size();i-->0;) + keySet(set,t._nextOther.get(i)); + } + } +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java new file mode 100644 index 00000000000..697ee43a62e --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java @@ -0,0 +1,113 @@ +// +// ======================================================================== +// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.util; + + +import java.nio.ByteBuffer; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class TrieTest +{ + Trie trie = new Trie<>(); + + @Before + public void before() + { + trie.put("hello",1); + trie.put("He",2); + trie.put("HELL",3); + trie.put("wibble",4); + trie.put("Wobble",5); + trie.put("foo-bar",6); + trie.put("foo+bar",7); + trie.put("HELL4",8); + + } + + @Test + public void testGetString() throws Exception + { + Assert.assertEquals(1,trie.get("hello").intValue()); + Assert.assertEquals(2,trie.get("He").intValue()); + Assert.assertEquals(3,trie.get("HELL").intValue()); + Assert.assertEquals(4,trie.get("wibble").intValue()); + Assert.assertEquals(5,trie.get("Wobble").intValue()); + Assert.assertEquals(6,trie.get("foo-bar").intValue()); + Assert.assertEquals(7,trie.get("foo+bar").intValue()); + + Assert.assertEquals(1,trie.get("Hello").intValue()); + Assert.assertEquals(2,trie.get("HE").intValue()); + Assert.assertEquals(3,trie.get("heLL").intValue()); + Assert.assertEquals(4,trie.get("Wibble").intValue()); + Assert.assertEquals(5,trie.get("wobble").intValue()); + Assert.assertEquals(6,trie.get("Foo-bar").intValue()); + Assert.assertEquals(7,trie.get("FOO+bar").intValue()); + + Assert.assertEquals(null,trie.get("Help")); + Assert.assertEquals(null,trie.get("Blah")); + } + + @Test + public void testGetBuffer() throws Exception + { + Assert.assertEquals(1,trie.get(BufferUtil.toBuffer("xhellox"),1,5).intValue()); + Assert.assertEquals(2,trie.get(BufferUtil.toBuffer("xhellox"),1,2).intValue()); + Assert.assertEquals(3,trie.get(BufferUtil.toBuffer("xhellox"),1,4).intValue()); + Assert.assertEquals(4,trie.get(BufferUtil.toBuffer("wibble"),0,6).intValue()); + Assert.assertEquals(5,trie.get(BufferUtil.toBuffer("xWobble"),1,6).intValue()); + Assert.assertEquals(6,trie.get(BufferUtil.toBuffer("xfoo-barx"),1,7).intValue()); + Assert.assertEquals(7,trie.get(BufferUtil.toBuffer("xfoo+barx"),1,7).intValue()); + + Assert.assertEquals(1,trie.get(BufferUtil.toBuffer("xhellox"),1,5).intValue()); + Assert.assertEquals(2,trie.get(BufferUtil.toBuffer("xHELLox"),1,2).intValue()); + Assert.assertEquals(3,trie.get(BufferUtil.toBuffer("xhellox"),1,4).intValue()); + Assert.assertEquals(4,trie.get(BufferUtil.toBuffer("Wibble"),0,6).intValue()); + Assert.assertEquals(5,trie.get(BufferUtil.toBuffer("xwobble"),1,6).intValue()); + Assert.assertEquals(6,trie.get(BufferUtil.toBuffer("xFOO-barx"),1,7).intValue()); + Assert.assertEquals(7,trie.get(BufferUtil.toBuffer("xFOO+barx"),1,7).intValue()); + + Assert.assertEquals(null,trie.get(BufferUtil.toBuffer("xHelpx"),1,4)); + Assert.assertEquals(null,trie.get(BufferUtil.toBuffer("xBlahx"),1,4)); + } + + + @Test + public void testGetBest() throws Exception + { + Assert.assertEquals(1,trie.getBest(BufferUtil.toBuffer("xhelloxxxx"),1,10).intValue()); + Assert.assertEquals(2,trie.getBest(BufferUtil.toBuffer("xhelxoxxxx"),1,10).intValue()); + Assert.assertEquals(3,trie.getBest(BufferUtil.toBuffer("xhellxxxxx"),1,10).intValue()); + Assert.assertEquals(6,trie.getBest(BufferUtil.toBuffer("xfoo-barxx"),1,10).intValue()); + Assert.assertEquals(8,trie.getBest(BufferUtil.toBuffer("xhell4xxxx"),1,10).intValue()); + + Assert.assertEquals(1,trie.getBest(BufferUtil.toBuffer("xHELLOxxxx"),1,10).intValue()); + Assert.assertEquals(2,trie.getBest(BufferUtil.toBuffer("xHELxoxxxx"),1,10).intValue()); + Assert.assertEquals(3,trie.getBest(BufferUtil.toBuffer("xHELLxxxxx"),1,10).intValue()); + Assert.assertEquals(6,trie.getBest(BufferUtil.toBuffer("xfoo-BARxx"),1,10).intValue()); + Assert.assertEquals(8,trie.getBest(BufferUtil.toBuffer("xHELL4xxxx"),1,10).intValue()); + + ByteBuffer buffer = (ByteBuffer)BufferUtil.toBuffer("xhelloxxxxxxx").position(2); + Assert.assertEquals(1,trie.getBest(buffer,-1,10).intValue()); + } + + +} diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/MuxAddHandler.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/MuxAddHandler.java index 696672392de..99aed4e497e 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/MuxAddHandler.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/MuxAddHandler.java @@ -92,7 +92,7 @@ public class MuxAddHandler implements MuxAddServer for (String headerName : request.getHeaders().keySet()) { - HttpHeader header = HttpHeader.lookAheadGet(headerName.getBytes(),0,headerName.length()); + HttpHeader header = HttpHeader.CACHE.getBest(headerName.getBytes(),0,headerName.length()); for (String value : request.getHeaders().get(headerName)) { httpChannel.parsedHeader(header,headerName,value);