Merged branch 'jetty-9.4.x' into 'jetty-10.0.x'.
This commit is contained in:
commit
2ba4cb2924
|
@ -276,12 +276,9 @@ public class HttpField
|
|||
|
||||
public boolean isSameName(HttpField field)
|
||||
{
|
||||
@SuppressWarnings("ReferenceEquality")
|
||||
boolean sameObject = (field==this);
|
||||
|
||||
if (field==null)
|
||||
return false;
|
||||
if (sameObject)
|
||||
if (field==this)
|
||||
return true;
|
||||
if (_header!=null && _header==field.getHeader())
|
||||
return true;
|
||||
|
|
|
@ -32,7 +32,6 @@ import java.util.Set;
|
|||
import java.util.StringTokenizer;
|
||||
import java.util.function.ToIntFunction;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
@ -44,7 +43,7 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
*
|
||||
* <p>This class is not synchronized as it is expected that modifications will only be performed by a
|
||||
* single thread.
|
||||
*
|
||||
*
|
||||
* <p>The cookie handling provided by this class is guided by the Servlet specification and RFC6265.
|
||||
*
|
||||
*/
|
||||
|
@ -54,33 +53,33 @@ public class HttpFields implements Iterable<HttpField>
|
|||
|
||||
private HttpField[] _fields;
|
||||
private int _size;
|
||||
|
||||
|
||||
/**
|
||||
* Initialize an empty HttpFields.
|
||||
*/
|
||||
public HttpFields()
|
||||
{
|
||||
_fields=new HttpField[20];
|
||||
this(16); // Based on small sample of Chrome requests.
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize an empty HttpFields.
|
||||
*
|
||||
*
|
||||
* @param capacity the capacity of the http fields
|
||||
*/
|
||||
public HttpFields(int capacity)
|
||||
{
|
||||
_fields=new HttpField[capacity];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize HttpFields from copy.
|
||||
*
|
||||
*
|
||||
* @param fields the fields to copy data from
|
||||
*/
|
||||
public HttpFields(HttpFields fields)
|
||||
{
|
||||
_fields=Arrays.copyOf(fields._fields,fields._fields.length+10);
|
||||
_fields=Arrays.copyOf(fields._fields,fields._fields.length);
|
||||
_size=fields._size;
|
||||
}
|
||||
|
||||
|
@ -88,22 +87,21 @@ public class HttpFields implements Iterable<HttpField>
|
|||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Iterator<HttpField> iterator()
|
||||
{
|
||||
return new Itr();
|
||||
return listIterator();
|
||||
}
|
||||
|
||||
public ListIterator<HttpField> listIterator()
|
||||
{
|
||||
return new Itr();
|
||||
return new ListItr();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Stream<HttpField> stream()
|
||||
{
|
||||
return StreamSupport.stream(Arrays.spliterator(_fields,0,_size),false);
|
||||
return Arrays.stream(_fields).limit(_size);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,13 +110,15 @@ public class HttpFields implements Iterable<HttpField>
|
|||
*/
|
||||
public Set<String> getFieldNamesCollection()
|
||||
{
|
||||
final Set<String> set = new HashSet<>(_size);
|
||||
for (HttpField f : this)
|
||||
Set<String> set = null;
|
||||
for (int i=0;i<_size;i++)
|
||||
{
|
||||
if (f!=null)
|
||||
set.add(f.getName());
|
||||
HttpField f=_fields[i];
|
||||
if (set==null)
|
||||
set = new HashSet<>();
|
||||
set.add(f.getName());
|
||||
}
|
||||
return set;
|
||||
return set==null?Collections.emptySet():set;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -133,7 +133,7 @@ public class HttpFields implements Iterable<HttpField>
|
|||
|
||||
/**
|
||||
* Get a Field by index.
|
||||
* @param index the field index
|
||||
* @param index the field index
|
||||
* @return A Field value or null if the Field value has not been set
|
||||
*/
|
||||
public HttpField getField(int index)
|
||||
|
@ -165,6 +165,22 @@ public class HttpFields implements Iterable<HttpField>
|
|||
return null;
|
||||
}
|
||||
|
||||
public List<HttpField> getFields(HttpHeader header)
|
||||
{
|
||||
List<HttpField> fields = null;
|
||||
for (int i=0;i<_size;i++)
|
||||
{
|
||||
HttpField f=_fields[i];
|
||||
if (f.getHeader()==header)
|
||||
{
|
||||
if (fields==null)
|
||||
fields = new ArrayList<>();
|
||||
fields.add(f);
|
||||
}
|
||||
}
|
||||
return fields==null?Collections.emptyList():fields;
|
||||
}
|
||||
|
||||
public boolean contains(HttpField field)
|
||||
{
|
||||
for (int i=_size;i-->0;)
|
||||
|
@ -186,7 +202,7 @@ public class HttpFields implements Iterable<HttpField>
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public boolean contains(String name, String value)
|
||||
{
|
||||
for (int i=_size;i-->0;)
|
||||
|
@ -208,7 +224,7 @@ public class HttpFields implements Iterable<HttpField>
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public boolean containsKey(String name)
|
||||
{
|
||||
for (int i=_size;i-->0;)
|
||||
|
@ -251,24 +267,30 @@ public class HttpFields implements Iterable<HttpField>
|
|||
public List<String> getValuesList(HttpHeader header)
|
||||
{
|
||||
final List<String> list = new ArrayList<>();
|
||||
for (HttpField f : this)
|
||||
if (f.getHeader()==header)
|
||||
for (int i=0;i<_size;i++)
|
||||
{
|
||||
HttpField f = _fields[i];
|
||||
if (f.getHeader() == header)
|
||||
list.add(f.getValue());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get multiple header of the same name
|
||||
*
|
||||
*
|
||||
* @return List the header values
|
||||
* @param name the case-insensitive field name
|
||||
*/
|
||||
public List<String> getValuesList(String name)
|
||||
{
|
||||
final List<String> list = new ArrayList<>();
|
||||
for (HttpField f : this)
|
||||
for (int i=0;i<_size;i++)
|
||||
{
|
||||
HttpField f = _fields[i];
|
||||
if (f.getName().equalsIgnoreCase(name))
|
||||
list.add(f.getValue());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
@ -283,8 +305,9 @@ public class HttpFields implements Iterable<HttpField>
|
|||
public boolean addCSV(HttpHeader header,String... values)
|
||||
{
|
||||
QuotedCSV existing = null;
|
||||
for (HttpField f : this)
|
||||
for (int i=0;i<_size;i++)
|
||||
{
|
||||
HttpField f = _fields[i];
|
||||
if (f.getHeader()==header)
|
||||
{
|
||||
if (existing==null)
|
||||
|
@ -292,7 +315,7 @@ public class HttpFields implements Iterable<HttpField>
|
|||
existing.addValue(f.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String value = addCSV(existing,values);
|
||||
if (value!=null)
|
||||
{
|
||||
|
@ -301,7 +324,7 @@ public class HttpFields implements Iterable<HttpField>
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add comma separated values, but only if not already
|
||||
* present.
|
||||
|
@ -312,8 +335,9 @@ public class HttpFields implements Iterable<HttpField>
|
|||
public boolean addCSV(String name,String... values)
|
||||
{
|
||||
QuotedCSV existing = null;
|
||||
for (HttpField f : this)
|
||||
for (int i=0;i<_size;i++)
|
||||
{
|
||||
HttpField f = _fields[i];
|
||||
if (f.getName().equalsIgnoreCase(name))
|
||||
{
|
||||
if (existing==null)
|
||||
|
@ -330,14 +354,14 @@ public class HttpFields implements Iterable<HttpField>
|
|||
return false;
|
||||
}
|
||||
|
||||
protected String addCSV(QuotedCSV existing,String... values)
|
||||
protected String addCSV(QuotedCSV existing, String... values)
|
||||
{
|
||||
// remove any existing values from the new values
|
||||
boolean add = true;
|
||||
if (existing!=null && !existing.isEmpty())
|
||||
{
|
||||
add = false;
|
||||
|
||||
|
||||
for (int i=values.length;i-->0;)
|
||||
{
|
||||
String unquoted = QuotedCSV.unquote(values[i]);
|
||||
|
@ -347,7 +371,7 @@ public class HttpFields implements Iterable<HttpField>
|
|||
add = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (add)
|
||||
{
|
||||
StringBuilder value = new StringBuilder();
|
||||
|
@ -362,12 +386,12 @@ public class HttpFields implements Iterable<HttpField>
|
|||
if (value.length()>0)
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get multiple field values of the same name, split
|
||||
* Get multiple field values of the same name, split
|
||||
* as a {@link QuotedCSV}
|
||||
*
|
||||
* @return List the values with OWS stripped
|
||||
|
@ -481,7 +505,7 @@ public class HttpFields implements Iterable<HttpField>
|
|||
for (int i=0;i<_size;i++)
|
||||
{
|
||||
final HttpField f = _fields[i];
|
||||
|
||||
|
||||
if (f.getName().equalsIgnoreCase(name) && f.getValue()!=null)
|
||||
{
|
||||
final int first=i;
|
||||
|
@ -495,7 +519,7 @@ public class HttpFields implements Iterable<HttpField>
|
|||
{
|
||||
if (field==null)
|
||||
{
|
||||
while (i<_size)
|
||||
while (i<_size)
|
||||
{
|
||||
field=_fields[i++];
|
||||
if (field.getName().equalsIgnoreCase(name) && field.getValue()!=null)
|
||||
|
@ -548,7 +572,7 @@ public class HttpFields implements Iterable<HttpField>
|
|||
if (!put)
|
||||
add(field);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set a field.
|
||||
*
|
||||
|
@ -840,7 +864,7 @@ public class HttpFields implements Iterable<HttpField>
|
|||
{
|
||||
_size=0;
|
||||
}
|
||||
|
||||
|
||||
public void add(HttpField field)
|
||||
{
|
||||
if (field!=null)
|
||||
|
@ -938,36 +962,36 @@ public class HttpFields implements Iterable<HttpField>
|
|||
return value.substring(0, i).trim();
|
||||
}
|
||||
|
||||
private class Itr implements ListIterator<HttpField>
|
||||
private class ListItr implements ListIterator<HttpField>
|
||||
{
|
||||
int _cursor; // index of next element to return
|
||||
int _last=-1;
|
||||
int _current =-1;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
public boolean hasNext()
|
||||
{
|
||||
return _cursor != _size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpField next()
|
||||
public HttpField next()
|
||||
{
|
||||
int i = _cursor;
|
||||
if (i >= _size)
|
||||
if (_cursor == _size)
|
||||
throw new NoSuchElementException();
|
||||
_cursor = i + 1;
|
||||
return _fields[_last=i];
|
||||
_current = _cursor++;
|
||||
return _fields[_current];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove()
|
||||
public void remove()
|
||||
{
|
||||
if (_last<0)
|
||||
if (_current <0)
|
||||
throw new IllegalStateException();
|
||||
|
||||
System.arraycopy(_fields,_last+1,_fields,_last,--_size-_last);
|
||||
_cursor=_last;
|
||||
_last=-1;
|
||||
_size--;
|
||||
System.arraycopy(_fields, _current +1,_fields, _current,_size- _current);
|
||||
_fields[_size]=null;
|
||||
_cursor= _current;
|
||||
_current =-1;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -981,7 +1005,8 @@ public class HttpFields implements Iterable<HttpField>
|
|||
{
|
||||
if (_cursor == 0)
|
||||
throw new NoSuchElementException();
|
||||
return _fields[_last=--_cursor];
|
||||
_current = --_cursor;
|
||||
return _fields[_current];
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -998,10 +1023,10 @@ public class HttpFields implements Iterable<HttpField>
|
|||
|
||||
@Override
|
||||
public void set(HttpField field)
|
||||
{
|
||||
if (_last<0)
|
||||
{
|
||||
if (_current <0)
|
||||
throw new IllegalStateException();
|
||||
_fields[_last] = field;
|
||||
_fields[_current] = field;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1010,8 +1035,7 @@ public class HttpFields implements Iterable<HttpField>
|
|||
_fields = Arrays.copyOf(_fields,_fields.length+1);
|
||||
System.arraycopy(_fields,_cursor,_fields,_cursor+1,_size++);
|
||||
_fields[_cursor++] = field;
|
||||
_last=-1;
|
||||
_current =-1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ public class MetaDataBuilder
|
|||
private HostPortHttpField _authority;
|
||||
private String _path;
|
||||
private long _contentLength=Long.MIN_VALUE;
|
||||
private HttpFields _fields = new HttpFields(10);
|
||||
private HttpFields _fields = new HttpFields();
|
||||
private HpackException.StreamException _streamException;
|
||||
private boolean _request;
|
||||
private boolean _response;
|
||||
|
@ -255,7 +255,7 @@ public class MetaDataBuilder
|
|||
}
|
||||
finally
|
||||
{
|
||||
_fields = new HttpFields(Math.max(10, fields.size() + 5));
|
||||
_fields = new HttpFields(Math.max(16, fields.size() + 5));
|
||||
_request = false;
|
||||
_response = false;
|
||||
_status = null;
|
||||
|
|
|
@ -203,6 +203,7 @@ public class Request implements HttpServletRequest
|
|||
private boolean _requestedSessionIdFromCookie = false;
|
||||
private Attributes _attributes;
|
||||
private Authentication _authentication;
|
||||
private String _contentType;
|
||||
private String _characterEncoding;
|
||||
private ContextHandler.Context _context;
|
||||
private Cookies _cookies;
|
||||
|
@ -454,8 +455,8 @@ public class Request implements HttpServletRequest
|
|||
int contentLength = getContentLength();
|
||||
if (contentLength != 0 && _inputState == __NONE)
|
||||
{
|
||||
contentType = HttpFields.valueParameters(contentType, null);
|
||||
if (MimeTypes.Type.FORM_ENCODED.is(contentType) &&
|
||||
String baseType = HttpFields.valueParameters(contentType, null);
|
||||
if (MimeTypes.Type.FORM_ENCODED.is(baseType) &&
|
||||
_channel.getHttpConfiguration().isFormEncodedMethod(getMethod()))
|
||||
{
|
||||
if (_metaData!=null)
|
||||
|
@ -466,7 +467,7 @@ public class Request implements HttpServletRequest
|
|||
}
|
||||
extractFormParameters(_contentParameters);
|
||||
}
|
||||
else if (MimeTypes.Type.MULTIPART_FORM_DATA.is(contentType) &&
|
||||
else if (MimeTypes.Type.MULTIPART_FORM_DATA.is(baseType) &&
|
||||
getAttribute(__MULTIPART_CONFIG_ELEMENT) != null &&
|
||||
_multiParts == null)
|
||||
{
|
||||
|
@ -670,7 +671,16 @@ public class Request implements HttpServletRequest
|
|||
public String getCharacterEncoding()
|
||||
{
|
||||
if (_characterEncoding==null)
|
||||
getContentType();
|
||||
{
|
||||
String contentType = getContentType();
|
||||
if (contentType!=null)
|
||||
{
|
||||
MimeTypes.Type mime = MimeTypes.CACHE.get(contentType);
|
||||
String charset = (mime == null || mime.getCharset() == null) ? MimeTypes.getCharsetFromContentType(contentType) : mime.getCharset().toString();
|
||||
if (charset != null)
|
||||
_characterEncoding=charset;
|
||||
}
|
||||
}
|
||||
return _characterEncoding;
|
||||
}
|
||||
|
||||
|
@ -726,16 +736,12 @@ public class Request implements HttpServletRequest
|
|||
@Override
|
||||
public String getContentType()
|
||||
{
|
||||
MetaData.Request metadata = _metaData;
|
||||
String content_type = metadata==null?null:metadata.getFields().get(HttpHeader.CONTENT_TYPE);
|
||||
if (_characterEncoding==null && content_type!=null)
|
||||
if (_contentType==null)
|
||||
{
|
||||
MimeTypes.Type mime = MimeTypes.CACHE.get(content_type);
|
||||
String charset = (mime == null || mime.getCharset() == null) ? MimeTypes.getCharsetFromContentType(content_type) : mime.getCharset().toString();
|
||||
if (charset != null)
|
||||
_characterEncoding=charset;
|
||||
MetaData.Request metadata = _metaData;
|
||||
_contentType = metadata == null ? null : metadata.getFields().get(HttpHeader.CONTENT_TYPE);
|
||||
}
|
||||
return content_type;
|
||||
return _contentType;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
@ -1818,6 +1824,7 @@ public class Request implements HttpServletRequest
|
|||
protected void recycle()
|
||||
{
|
||||
_metaData=null;
|
||||
_originalUri=null;
|
||||
|
||||
if (_context != null)
|
||||
throw new IllegalStateException("Request in context!");
|
||||
|
@ -1847,6 +1854,7 @@ public class Request implements HttpServletRequest
|
|||
_handled = false;
|
||||
if (_attributes != null)
|
||||
_attributes.clearAttributes();
|
||||
_contentType = null;
|
||||
_characterEncoding = null;
|
||||
_contextPath = null;
|
||||
if (_cookies != null)
|
||||
|
@ -2005,10 +2013,8 @@ public class Request implements HttpServletRequest
|
|||
* @see javax.servlet.ServletRequest#getContentType()
|
||||
*/
|
||||
public void setContentType(String contentType)
|
||||
{
|
||||
MetaData.Request metadata = _metaData;
|
||||
if (metadata!=null)
|
||||
metadata.getFields().put(HttpHeader.CONTENT_TYPE,contentType);
|
||||
{
|
||||
_contentType = contentType;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
@ -2319,8 +2325,8 @@ public class Request implements HttpServletRequest
|
|||
@Override
|
||||
public Collection<Part> getParts() throws IOException, ServletException
|
||||
{
|
||||
if (getContentType() == null ||
|
||||
!MimeTypes.Type.MULTIPART_FORM_DATA.is(HttpFields.valueParameters(getContentType(),null)))
|
||||
String contentType = getContentType();
|
||||
if (contentType == null || !MimeTypes.Type.MULTIPART_FORM_DATA.is(HttpFields.valueParameters(contentType,null)))
|
||||
throw new ServletException("Content-Type != multipart/form-data");
|
||||
return getParts(null);
|
||||
}
|
||||
|
@ -2405,7 +2411,7 @@ public class Request implements HttpServletRequest
|
|||
|
||||
private MultiParts newMultiParts(ServletInputStream inputStream, String contentType, MultipartConfigElement config, Object object) throws IOException
|
||||
{
|
||||
return new MultiParts.MultiPartsHttpParser(getInputStream(), getContentType(), config,
|
||||
return new MultiParts.MultiPartsHttpParser(getInputStream(), contentType, config,
|
||||
(_context != null ? (File) _context.getAttribute("javax.servlet.context.tempdir") : null), this);
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,6 @@ import java.util.Locale;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.Cookie;
|
||||
|
@ -1042,37 +1041,27 @@ public class Response implements HttpServletResponse
|
|||
_reason = null;
|
||||
_contentLength = -1;
|
||||
|
||||
List<HttpField> cookies = preserveCookies
|
||||
?_fields.stream()
|
||||
.filter(f->f.getHeader()==HttpHeader.SET_COOKIE)
|
||||
.collect(Collectors.toList()):null;
|
||||
|
||||
List<HttpField> cookies = preserveCookies ?_fields.getFields(HttpHeader.SET_COOKIE):null;
|
||||
_fields.clear();
|
||||
|
||||
String connection = _channel.getRequest().getHeader(HttpHeader.CONNECTION.asString());
|
||||
if (connection != null)
|
||||
for (String value: _channel.getRequest().getHttpFields().getCSV(HttpHeader.CONNECTION,false))
|
||||
{
|
||||
for (String value: StringUtil.csvSplit(null,connection,0,connection.length()))
|
||||
HttpHeaderValue cb = HttpHeaderValue.CACHE.get(value);
|
||||
if (cb != null)
|
||||
{
|
||||
HttpHeaderValue cb = HttpHeaderValue.CACHE.get(value);
|
||||
|
||||
if (cb != null)
|
||||
switch (cb)
|
||||
{
|
||||
switch (cb)
|
||||
{
|
||||
case CLOSE:
|
||||
_fields.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.toString());
|
||||
break;
|
||||
|
||||
case KEEP_ALIVE:
|
||||
if (HttpVersion.HTTP_1_0.is(_channel.getRequest().getProtocol()))
|
||||
_fields.put(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.toString());
|
||||
break;
|
||||
case TE:
|
||||
_fields.put(HttpHeader.CONNECTION, HttpHeaderValue.TE.toString());
|
||||
break;
|
||||
default:
|
||||
}
|
||||
case CLOSE:
|
||||
_fields.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.toString());
|
||||
break;
|
||||
case KEEP_ALIVE:
|
||||
if (HttpVersion.HTTP_1_0.is(_channel.getRequest().getProtocol()))
|
||||
_fields.put(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.toString());
|
||||
break;
|
||||
case TE:
|
||||
_fields.put(HttpHeader.CONNECTION, HttpHeaderValue.TE.toString());
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
|
|
|
@ -0,0 +1,341 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 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.server.jmh;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Level;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.Param;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.Setup;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Threads;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
import org.openjdk.jmh.runner.Runner;
|
||||
import org.openjdk.jmh.runner.RunnerException;
|
||||
import org.openjdk.jmh.runner.options.Options;
|
||||
import org.openjdk.jmh.runner.options.OptionsBuilder;
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
@Threads(1)
|
||||
@Warmup(iterations = 6, time = 2000, timeUnit = TimeUnit.MILLISECONDS)
|
||||
@Measurement(iterations = 3, time = 2000, timeUnit = TimeUnit.MILLISECONDS)
|
||||
public class ListVsMapBenchmark
|
||||
{
|
||||
@Param({ "12" }) // Chrome has 12 for HTTP/1.1 and 16 for HTTP/2 (including meta headers)
|
||||
public static int size;
|
||||
|
||||
@Param({ "11" }) // average length of known headers in HttpHeader
|
||||
public static int length;
|
||||
|
||||
@Param({"1", "10", "20", "30" })
|
||||
public static int lookups;
|
||||
|
||||
@Param({"hits", "misses", "iterate" })
|
||||
public static String mostly;
|
||||
|
||||
static final String base = "This-is-the-base-of-All-key-names-and-is-long".substring(0,length);
|
||||
static final String miss = "X-" + base;
|
||||
static final List<String> trials = new ArrayList<>();
|
||||
static final Random random = new Random();
|
||||
|
||||
@Setup(Level.Trial)
|
||||
public void setup()
|
||||
{
|
||||
int hits = 1;
|
||||
int misses = 1;
|
||||
switch(mostly)
|
||||
{
|
||||
case "hits" : hits = lookups; break;
|
||||
case "misses" : misses = lookups; break;
|
||||
case "iterate" : hits = lookups/2; misses=lookups-hits; break;
|
||||
default : throw new IllegalStateException();
|
||||
}
|
||||
|
||||
for (int h = hits; h-->0;)
|
||||
trials.add(base + "-" + (h % size));
|
||||
|
||||
for (int m = misses; m-->0; )
|
||||
trials.add(miss);
|
||||
|
||||
Collections.shuffle(trials);
|
||||
}
|
||||
|
||||
|
||||
static class Pair
|
||||
{
|
||||
final String key;
|
||||
final String value;
|
||||
|
||||
public Pair(String key, String value)
|
||||
{
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
interface Fill
|
||||
{
|
||||
void put(Pair p);
|
||||
}
|
||||
|
||||
interface Lookup
|
||||
{
|
||||
Pair get(String key);
|
||||
Iterator<Pair> iterate();
|
||||
}
|
||||
|
||||
private void fill(Fill fill)
|
||||
{
|
||||
for (int i=0; i<size-1; i++)
|
||||
{
|
||||
String key = base + "-" + i;
|
||||
Pair pair = new Pair(key, Long.toString(random.nextLong(),8));
|
||||
fill.put(pair);
|
||||
}
|
||||
|
||||
// double up on header 0
|
||||
String key = base + "-0";
|
||||
Pair pair = new Pair(key, Long.toString(random.nextLong(),8));
|
||||
fill.put(pair);
|
||||
}
|
||||
|
||||
private long test(Lookup lookup)
|
||||
{
|
||||
long result = 0;
|
||||
if ("iterate".equals(mostly))
|
||||
{
|
||||
Iterator<String> t = trials.iterator();
|
||||
while(t.hasNext())
|
||||
{
|
||||
// Look for 4 headers at once because that is what the common case of a
|
||||
// ResourceService does
|
||||
String one = t.hasNext() ? t.next() : null;
|
||||
String two = t.hasNext() ? t.next() : null;
|
||||
String three = t.hasNext() ? t.next() : null;
|
||||
String four = t.hasNext() ? t.next() : null;
|
||||
|
||||
Iterator<Pair> i = lookup.iterate();
|
||||
while (i.hasNext())
|
||||
{
|
||||
Pair p = i.next();
|
||||
String k = p.key;
|
||||
if (one != null && one.equals(k))
|
||||
result ^= p.value.hashCode();
|
||||
else if (two != null && two.equals(k))
|
||||
result ^= p.value.hashCode();
|
||||
else if (three != null && three.equals(k))
|
||||
result ^= p.value.hashCode();
|
||||
else if (four != null && four.equals(k))
|
||||
result ^= p.value.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (String t : trials)
|
||||
{
|
||||
Pair p = lookup.get(t);
|
||||
if (p != null)
|
||||
result ^= p.value.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private long listLookup(List<Pair> list)
|
||||
{
|
||||
return test(new Lookup() {
|
||||
@Override
|
||||
public Pair get(String k)
|
||||
{
|
||||
for (int i = 0; i<list.size(); i++ )
|
||||
{
|
||||
Pair p = list.get(i);
|
||||
if (p.key.equalsIgnoreCase(k))
|
||||
return p;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Pair> iterate()
|
||||
{
|
||||
return list.iterator();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput})
|
||||
public long testArrayList() throws Exception
|
||||
{
|
||||
List<Pair> list = new ArrayList<>(size);
|
||||
fill(list::add);
|
||||
return listLookup(list);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput})
|
||||
public long testLinkedList() throws Exception
|
||||
{
|
||||
List<Pair> list = new LinkedList<>();
|
||||
fill(list::add);
|
||||
return listLookup(list);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput})
|
||||
public long testLinkedHashMap() throws Exception
|
||||
{
|
||||
// This loses the true ordering of fields
|
||||
Map<String,List<Pair>> map = new LinkedHashMap<>(size);
|
||||
fill(p->
|
||||
{
|
||||
List<Pair> list = new LinkedList<>();
|
||||
list.add(p);
|
||||
map.put(p.key.toLowerCase(),list);
|
||||
});
|
||||
return test(new Lookup()
|
||||
{
|
||||
@Override
|
||||
public Pair get(String k)
|
||||
{
|
||||
List<Pair> list = map.get(k.toLowerCase());
|
||||
if (list == null || list.isEmpty())
|
||||
return null;
|
||||
return list.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Pair> iterate()
|
||||
{
|
||||
Iterator<List<Pair>> iter = map.values().iterator();
|
||||
|
||||
return new Iterator<Pair>() {
|
||||
Iterator<Pair> current;
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
if (( current==null || !current.hasNext() ) && iter.hasNext())
|
||||
current=iter.next().iterator();
|
||||
return current!=null && current.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair next()
|
||||
{
|
||||
if (hasNext())
|
||||
return current.next();
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput})
|
||||
public long testHashMapAndLinkedList() throws Exception
|
||||
{
|
||||
// This keeps the true ordering of fields
|
||||
Map<String,List<Pair>> map = new HashMap<>(size);
|
||||
List<Pair> order = new LinkedList<>();
|
||||
|
||||
fill(p->
|
||||
{
|
||||
List<Pair> list = new LinkedList<>();
|
||||
list.add(p);
|
||||
map.put(p.key.toLowerCase(),list);
|
||||
order.add(p);
|
||||
});
|
||||
return mapLookup(map, order);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput})
|
||||
public long testHashMapAndArrayList() throws Exception
|
||||
{
|
||||
// This keeps the true ordering of fields
|
||||
Map<String,List<Pair>> map = new HashMap<>(size);
|
||||
List<Pair> order = new ArrayList<>();
|
||||
|
||||
fill(p->
|
||||
{
|
||||
List<Pair> list = new ArrayList<>(2);
|
||||
list.add(p);
|
||||
map.put(p.key.toLowerCase(),list);
|
||||
order.add(p);
|
||||
});
|
||||
return mapLookup(map, order);
|
||||
}
|
||||
|
||||
private long mapLookup(Map<String, List<Pair>> map, List<Pair> order)
|
||||
{
|
||||
return test(new Lookup()
|
||||
{
|
||||
@Override
|
||||
public Pair get(String k)
|
||||
{
|
||||
List<Pair> list = map.get(k.toLowerCase());
|
||||
if (list == null || list.isEmpty())
|
||||
return null;
|
||||
return list.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Pair> iterate()
|
||||
{
|
||||
return order.iterator();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) throws RunnerException
|
||||
{
|
||||
Options opt = new OptionsBuilder()
|
||||
.include(ListVsMapBenchmark.class.getSimpleName())
|
||||
// .addProfiler(GCProfiler.class)
|
||||
.forks(1)
|
||||
.build();
|
||||
|
||||
new Runner(opt).run();
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue