Merged branch 'jetty-9.4.x' into 'master'.
This commit is contained in:
commit
a1cc30c751
|
@ -271,7 +271,9 @@ public class MultiPartContentProvider extends AbstractTypedContentProvider imple
|
|||
continue;
|
||||
buffer.write(field.getName().getBytes(StandardCharsets.US_ASCII));
|
||||
buffer.write(COLON_SPACE_BYTES);
|
||||
buffer.write(field.getValue().getBytes(StandardCharsets.UTF_8));
|
||||
String value = field.getValue();
|
||||
if (value != null)
|
||||
buffer.write(value.getBytes(StandardCharsets.UTF_8));
|
||||
buffer.write(CR_LF_BYTES);
|
||||
}
|
||||
buffer.write(CR_LF_BYTES);
|
||||
|
|
|
@ -83,6 +83,9 @@ public class HttpField
|
|||
|
||||
public String[] getValues()
|
||||
{
|
||||
if (_value == null)
|
||||
return null;
|
||||
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
int state = 0;
|
||||
int start=0;
|
||||
|
@ -191,8 +194,8 @@ public class HttpField
|
|||
return list.toArray(new String[list.size()]);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Look for a value in a possible multi valued field
|
||||
/**
|
||||
* Look for a value in a possible multi valued field
|
||||
* @param search Values to search for (case insensitive)
|
||||
* @return True iff the value is contained in the field value entirely or
|
||||
* as an element of a quoted comma separated list. List element parameters (eg qualities) are ignored,
|
||||
|
@ -208,7 +211,7 @@ public class HttpField
|
|||
return false;
|
||||
if (search.equals(_value))
|
||||
return true;
|
||||
|
||||
|
||||
search = StringUtil.asciiToLowerCase(search);
|
||||
|
||||
int state=0;
|
||||
|
@ -412,7 +415,7 @@ public class HttpField
|
|||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int vhc = _value==null?0:_value.hashCode();
|
||||
int vhc = Objects.hashCode(_value);
|
||||
if (_header==null)
|
||||
return vhc ^ nameHashCode();
|
||||
return vhc ^ _header.hashCode();
|
||||
|
|
|
@ -35,9 +35,8 @@ import org.eclipse.jetty.util.Trie;
|
|||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** HPACK - Header Compression for HTTP/2
|
||||
/**
|
||||
* HPACK - Header Compression for HTTP/2
|
||||
* <p>This class maintains the compression context for a single HTTP/2
|
||||
* connection. Specifically it holds the static and dynamic Header Field Tables
|
||||
* and the associated sizes and limits.
|
||||
|
@ -48,7 +47,7 @@ public class HpackContext
|
|||
{
|
||||
public static final Logger LOG = Log.getLogger(HpackContext.class);
|
||||
private static final String EMPTY = "";
|
||||
public static final String[][] STATIC_TABLE =
|
||||
public static final String[][] STATIC_TABLE =
|
||||
{
|
||||
{null,null},
|
||||
/* 1 */ {":authority",EMPTY},
|
||||
|
@ -113,7 +112,7 @@ public class HpackContext
|
|||
/* 60 */ {"via",EMPTY},
|
||||
/* 61 */ {"www-authenticate",EMPTY},
|
||||
};
|
||||
|
||||
|
||||
private static final Map<HttpField,Entry> __staticFieldMap = new HashMap<>();
|
||||
private static final Trie<StaticEntry> __staticNameMap = new ArrayTernaryTrie<>(true,512);
|
||||
private static final StaticEntry[] __staticTableByHeader = new StaticEntry[HttpHeader.UNKNOWN.ordinal()];
|
||||
|
@ -134,42 +133,42 @@ public class HpackContext
|
|||
{
|
||||
case C_METHOD:
|
||||
{
|
||||
|
||||
|
||||
HttpMethod method = HttpMethod.CACHE.get(value);
|
||||
if (method!=null)
|
||||
entry=new StaticEntry(i,new StaticTableHttpField(header,name,value,method));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case C_SCHEME:
|
||||
{
|
||||
|
||||
|
||||
HttpScheme scheme = HttpScheme.CACHE.get(value);
|
||||
if (scheme!=null)
|
||||
entry=new StaticEntry(i,new StaticTableHttpField(header,name,value,scheme));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case C_STATUS:
|
||||
{
|
||||
entry=new StaticEntry(i,new StaticTableHttpField(header,name,value,Integer.valueOf(value)));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (entry==null)
|
||||
entry=new StaticEntry(i,header==null?new HttpField(STATIC_TABLE[i][0],value):new HttpField(header,name,value));
|
||||
|
||||
|
||||
|
||||
|
||||
__staticTable[i]=entry;
|
||||
|
||||
|
||||
if (entry._field.getValue()!=null)
|
||||
__staticFieldMap.put(entry._field,entry);
|
||||
|
||||
|
||||
if (!added.contains(entry._field.getName()))
|
||||
{
|
||||
added.add(entry._field.getName());
|
||||
|
@ -178,7 +177,7 @@ public class HpackContext
|
|||
throw new IllegalStateException("name trie too small");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (HttpHeader h : HttpHeader.values())
|
||||
{
|
||||
StaticEntry entry = __staticNameMap.get(h.asString());
|
||||
|
@ -186,13 +185,13 @@ public class HpackContext
|
|||
__staticTableByHeader[h.ordinal()]=entry;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int _maxDynamicTableSizeInBytes;
|
||||
private int _dynamicTableSizeInBytes;
|
||||
private final DynamicTable _dynamicTable;
|
||||
private final Map<HttpField,Entry> _fieldMap = new HashMap<>();
|
||||
private final Map<String,Entry> _nameMap = new HashMap<>();
|
||||
|
||||
|
||||
HpackContext(int maxDynamicTableSize)
|
||||
{
|
||||
_maxDynamicTableSizeInBytes=maxDynamicTableSize;
|
||||
|
@ -201,7 +200,7 @@ public class HpackContext
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(String.format("HdrTbl[%x] created max=%d",hashCode(),maxDynamicTableSize));
|
||||
}
|
||||
|
||||
|
||||
public void resize(int newMaxDynamicTableSize)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
|
@ -211,7 +210,7 @@ public class HpackContext
|
|||
evict();
|
||||
_dynamicTable.resizeUnsafe(guesstimateEntries);
|
||||
}
|
||||
|
||||
|
||||
public Entry get(HttpField field)
|
||||
{
|
||||
Entry entry = _fieldMap.get(field);
|
||||
|
@ -219,7 +218,7 @@ public class HpackContext
|
|||
entry=__staticFieldMap.get(field);
|
||||
return entry;
|
||||
}
|
||||
|
||||
|
||||
public Entry get(String name)
|
||||
{
|
||||
Entry entry = __staticNameMap.get(name);
|
||||
|
@ -227,19 +226,19 @@ public class HpackContext
|
|||
return entry;
|
||||
return _nameMap.get(StringUtil.asciiToLowerCase(name));
|
||||
}
|
||||
|
||||
|
||||
public Entry get(int index)
|
||||
{
|
||||
if (index<__staticTable.length)
|
||||
return __staticTable[index];
|
||||
|
||||
|
||||
int d=_dynamicTable.size()-index+__staticTable.length-1;
|
||||
|
||||
if (d>=0)
|
||||
return _dynamicTable.getUnsafe(d);
|
||||
if (d>=0)
|
||||
return _dynamicTable.getUnsafe(d);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public Entry get(HttpHeader header)
|
||||
{
|
||||
Entry e = __staticTableByHeader[header.ordinal()];
|
||||
|
@ -252,7 +251,7 @@ public class HpackContext
|
|||
{
|
||||
return __staticTableByHeader[header.ordinal()];
|
||||
}
|
||||
|
||||
|
||||
public Entry add(HttpField field)
|
||||
{
|
||||
int slot=_dynamicTable.getNextSlotUnsafe();
|
||||
|
@ -282,7 +281,7 @@ public class HpackContext
|
|||
{
|
||||
return _dynamicTable.size();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Current Dynamic table size in Octets
|
||||
*/
|
||||
|
@ -308,7 +307,7 @@ public class HpackContext
|
|||
|
||||
return _dynamicTable.index(entry)+__staticTable.length-1;
|
||||
}
|
||||
|
||||
|
||||
public static int staticIndex(HttpHeader header)
|
||||
{
|
||||
if (header==null)
|
||||
|
@ -318,7 +317,7 @@ public class HpackContext
|
|||
return 0;
|
||||
return entry.getSlot();
|
||||
}
|
||||
|
||||
|
||||
private void evict()
|
||||
{
|
||||
while (_dynamicTableSizeInBytes>_maxDynamicTableSizeInBytes)
|
||||
|
@ -336,31 +335,20 @@ public class HpackContext
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(String.format("HdrTbl[%x] entries=%d, size=%d, max=%d",hashCode(),_dynamicTable.size(),_dynamicTableSizeInBytes,_maxDynamicTableSizeInBytes));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("HpackContext@%x{entries=%d,size=%d,max=%d}",hashCode(),_dynamicTable.size(),_dynamicTableSizeInBytes,_maxDynamicTableSizeInBytes);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
*/
|
||||
|
||||
private class DynamicTable extends ArrayQueue<HpackContext.Entry>
|
||||
{
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param initCapacity
|
||||
* @param growBy
|
||||
*/
|
||||
private DynamicTable(int initCapacity, int growBy)
|
||||
{
|
||||
super(initCapacity,growBy);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @see org.eclipse.jetty.util.ArrayQueue#growUnsafe()
|
||||
*/
|
||||
|
@ -373,7 +361,6 @@ public class HpackContext
|
|||
((Entry)_elements[s])._slot=s;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @see org.eclipse.jetty.util.ArrayQueue#enqueue(java.lang.Object)
|
||||
*/
|
||||
|
@ -383,7 +370,6 @@ public class HpackContext
|
|||
return super.enqueue(e);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @see org.eclipse.jetty.util.ArrayQueue#dequeue()
|
||||
*/
|
||||
|
@ -392,85 +378,75 @@ public class HpackContext
|
|||
{
|
||||
return super.dequeue();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param entry
|
||||
* @return
|
||||
*/
|
||||
|
||||
private int index(Entry entry)
|
||||
{
|
||||
return entry._slot>=_nextE?_size-entry._slot+_nextE:_nextSlot-entry._slot;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
public static class Entry
|
||||
{
|
||||
final HttpField _field;
|
||||
int _slot;
|
||||
|
||||
|
||||
Entry()
|
||||
{
|
||||
{
|
||||
_slot=0;
|
||||
_field=null;
|
||||
}
|
||||
|
||||
|
||||
Entry(int index,String name, String value)
|
||||
{
|
||||
{
|
||||
_slot=index;
|
||||
_field=new HttpField(name,value);
|
||||
}
|
||||
|
||||
|
||||
Entry(int slot, HttpField field)
|
||||
{
|
||||
{
|
||||
_slot=slot;
|
||||
_field=field;
|
||||
}
|
||||
|
||||
public int getSize()
|
||||
{
|
||||
return 32+_field.getName().length()+_field.getValue().length();
|
||||
String value = _field.getValue();
|
||||
return 32 + _field.getName().length() + (value == null ? 0 : value.length());
|
||||
}
|
||||
|
||||
|
||||
public HttpField getHttpField()
|
||||
{
|
||||
return _field;
|
||||
}
|
||||
|
||||
|
||||
public boolean isStatic()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public byte[] getStaticHuffmanValue()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public int getSlot()
|
||||
{
|
||||
return _slot;
|
||||
}
|
||||
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return String.format("{%s,%d,%s,%x}",isStatic()?"S":"D",_slot,_field,hashCode());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class StaticEntry extends Entry
|
||||
{
|
||||
private final byte[] _huffmanValue;
|
||||
private final byte _encodedField;
|
||||
|
||||
|
||||
StaticEntry(int index,HttpField field)
|
||||
{
|
||||
{
|
||||
super(index,field);
|
||||
String value = field.getValue();
|
||||
if (value!=null && value.length()>0)
|
||||
|
@ -478,18 +454,18 @@ public class HpackContext
|
|||
int huffmanLen = Huffman.octetsNeeded(value);
|
||||
int lenLen = NBitInteger.octectsNeeded(7,huffmanLen);
|
||||
_huffmanValue = new byte[1+lenLen+huffmanLen];
|
||||
ByteBuffer buffer = ByteBuffer.wrap(_huffmanValue);
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(_huffmanValue);
|
||||
|
||||
// Indicate Huffman
|
||||
buffer.put((byte)0x80);
|
||||
// Add huffman length
|
||||
NBitInteger.encode(buffer,7,huffmanLen);
|
||||
// Encode value
|
||||
Huffman.encode(buffer,value);
|
||||
Huffman.encode(buffer,value);
|
||||
}
|
||||
else
|
||||
_huffmanValue=null;
|
||||
|
||||
|
||||
_encodedField=(byte)(0x80|index);
|
||||
}
|
||||
|
||||
|
@ -498,18 +474,16 @@ public class HpackContext
|
|||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] getStaticHuffmanValue()
|
||||
{
|
||||
return _huffmanValue;
|
||||
}
|
||||
|
||||
|
||||
public byte getEncodedField()
|
||||
{
|
||||
return _encodedField;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -24,25 +24,24 @@ import org.eclipse.jetty.http.BadMessageException;
|
|||
import org.eclipse.jetty.http.HostPortHttpField;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
|
||||
public class MetaDataBuilder
|
||||
{
|
||||
{
|
||||
private final int _maxSize;
|
||||
private int _size;
|
||||
private int _status;
|
||||
private String _method;
|
||||
private HttpScheme _scheme;
|
||||
private HostPortHttpField _authority;
|
||||
private String _path;
|
||||
private String _path;
|
||||
private long _contentLength=Long.MIN_VALUE;
|
||||
|
||||
private HttpFields _fields = new HttpFields(10);
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* @param maxHeadersSize The maximum size of the headers, expressed as total name and value characters.
|
||||
*/
|
||||
|
@ -50,7 +49,7 @@ public class MetaDataBuilder
|
|||
{
|
||||
_maxSize=maxHeadersSize;
|
||||
}
|
||||
|
||||
|
||||
/** Get the maxSize.
|
||||
* @return the maxSize
|
||||
*/
|
||||
|
@ -68,88 +67,101 @@ public class MetaDataBuilder
|
|||
}
|
||||
|
||||
public void emit(HttpField field)
|
||||
{
|
||||
int field_size = field.getName().length()+field.getValue().length();
|
||||
{
|
||||
HttpHeader header = field.getHeader();
|
||||
String name = field.getName();
|
||||
String value = field.getValue();
|
||||
int field_size = name.length() + (value == null ? 0 : value.length());
|
||||
_size+=field_size;
|
||||
if (_size>_maxSize)
|
||||
throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413,"Header size "+_size+">"+_maxSize);
|
||||
|
||||
|
||||
if (field instanceof StaticTableHttpField)
|
||||
{
|
||||
StaticTableHttpField value = (StaticTableHttpField)field;
|
||||
switch(field.getHeader())
|
||||
StaticTableHttpField staticField = (StaticTableHttpField)field;
|
||||
switch(header)
|
||||
{
|
||||
case C_STATUS:
|
||||
_status=(Integer)value.getStaticValue();
|
||||
_status=(Integer)staticField.getStaticValue();
|
||||
break;
|
||||
|
||||
|
||||
case C_METHOD:
|
||||
_method=field.getValue();
|
||||
_method=value;
|
||||
break;
|
||||
|
||||
case C_SCHEME:
|
||||
_scheme = (HttpScheme)value.getStaticValue();
|
||||
_scheme = (HttpScheme)staticField.getStaticValue();
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException(field.getName());
|
||||
throw new IllegalArgumentException(name);
|
||||
}
|
||||
}
|
||||
else if (field.getHeader()!=null)
|
||||
else if (header!=null)
|
||||
{
|
||||
switch(field.getHeader())
|
||||
switch(header)
|
||||
{
|
||||
case C_STATUS:
|
||||
_status=field.getIntValue();
|
||||
break;
|
||||
|
||||
case C_METHOD:
|
||||
_method=field.getValue();
|
||||
_method=value;
|
||||
break;
|
||||
|
||||
case C_SCHEME:
|
||||
_scheme = HttpScheme.CACHE.get(field.getValue());
|
||||
if (value != null)
|
||||
_scheme = HttpScheme.CACHE.get(value);
|
||||
break;
|
||||
|
||||
case C_AUTHORITY:
|
||||
_authority=(field instanceof HostPortHttpField)?((HostPortHttpField)field):new AuthorityHttpField(field.getValue());
|
||||
if (field instanceof HostPortHttpField)
|
||||
_authority = (HostPortHttpField)field;
|
||||
else if (value != null)
|
||||
_authority = new AuthorityHttpField(value);
|
||||
break;
|
||||
|
||||
case HOST:
|
||||
// :authority fields must come first. If we have one, ignore the host header as far as authority goes.
|
||||
if (_authority==null)
|
||||
_authority=(field instanceof HostPortHttpField)?((HostPortHttpField)field):new AuthorityHttpField(field.getValue());
|
||||
{
|
||||
if (field instanceof HostPortHttpField)
|
||||
_authority = (HostPortHttpField)field;
|
||||
else if (value != null)
|
||||
_authority = new AuthorityHttpField(value);
|
||||
}
|
||||
_fields.add(field);
|
||||
break;
|
||||
|
||||
case C_PATH:
|
||||
_path = field.getValue();
|
||||
_path = value;
|
||||
break;
|
||||
|
||||
case CONTENT_LENGTH:
|
||||
_contentLength = field.getLongValue();
|
||||
_fields.add(field);
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
if (field.getName().charAt(0)!=':')
|
||||
if (name.charAt(0)!=':')
|
||||
_fields.add(field);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (field.getName().charAt(0)!=':')
|
||||
if (name.charAt(0)!=':')
|
||||
_fields.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public MetaData build()
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpFields fields = _fields;
|
||||
_fields = new HttpFields(Math.max(10,fields.size()+5));
|
||||
|
||||
|
||||
if (_method!=null)
|
||||
return new MetaData.Request(_method,_scheme,_authority,_path,HttpVersion.HTTP_2,fields,_contentLength);
|
||||
if (_status!=0)
|
||||
|
@ -168,8 +180,8 @@ public class MetaDataBuilder
|
|||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Check that the max size will not be exceeded.
|
||||
/**
|
||||
* Check that the max size will not be exceeded.
|
||||
* @param length the length
|
||||
* @param huffman the huffman name
|
||||
*/
|
||||
|
|
|
@ -943,14 +943,14 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
|
||||
switch(_state.get())
|
||||
{
|
||||
case CLOSED:
|
||||
// Even though a write is not possible, because a close has
|
||||
// occurred, we need to call onWritePossible to tell async
|
||||
// producer that the last write completed.
|
||||
// So fall through
|
||||
case ASYNC:
|
||||
case READY:
|
||||
case PENDING:
|
||||
case UNREADY:
|
||||
case READY:
|
||||
// Even though a write is not possible, because a close has
|
||||
// occurred, we need to call onWritePossible to tell async
|
||||
// producer that the last write completed, so fall through.
|
||||
case CLOSED:
|
||||
try
|
||||
{
|
||||
_writeListener.onWritePossible();
|
||||
|
|
|
@ -18,19 +18,14 @@
|
|||
|
||||
package org.eclipse.jetty.servlet;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
@ -47,6 +42,8 @@ import javax.servlet.http.HttpServlet;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.server.HttpChannel;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
|
@ -59,11 +56,20 @@ import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser;
|
|||
import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.log.StacklessLogging;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith (AdvancedRunner.class)
|
||||
public class AsyncIOServletTest
|
||||
{
|
||||
|
@ -102,7 +108,6 @@ public class AsyncIOServletTest
|
|||
throw new IllegalStateException();
|
||||
}
|
||||
scope.set(new Throwable());
|
||||
|
||||
}
|
||||
@Override
|
||||
public void exitScope(Context context, Request request)
|
||||
|
@ -110,20 +115,18 @@ public class AsyncIOServletTest
|
|||
if (scope.get()==null)
|
||||
throw new IllegalStateException();
|
||||
scope.set(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
server.start();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void assertScope()
|
||||
{
|
||||
if (scope.get()==null)
|
||||
Assert.fail("Not in scope");
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
public void stopServer() throws Exception
|
||||
{
|
||||
|
@ -186,7 +189,7 @@ public class AsyncIOServletTest
|
|||
Assert.assertThat("onError message",t.getMessage(),is(throwable.getMessage()));
|
||||
latch.countDown();
|
||||
response.setStatus(500);
|
||||
|
||||
|
||||
asyncContext.complete();
|
||||
}
|
||||
});
|
||||
|
@ -215,8 +218,8 @@ public class AsyncIOServletTest
|
|||
line=in.readLine();
|
||||
}
|
||||
line=in.readLine();
|
||||
|
||||
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -303,7 +306,7 @@ public class AsyncIOServletTest
|
|||
response.flushBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
final AsyncContext asyncContext = request.startAsync(request, response);
|
||||
request.getInputStream().setReadListener(new ReadListener()
|
||||
{
|
||||
|
@ -414,11 +417,11 @@ public class AsyncIOServletTest
|
|||
SimpleHttpParser parser = new SimpleHttpParser();
|
||||
SimpleHttpResponse response = parser.readResponse(new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")));
|
||||
|
||||
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
Assert.assertEquals("500", response.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testAsyncWriteClosed() throws Exception
|
||||
|
@ -428,7 +431,7 @@ public class AsyncIOServletTest
|
|||
for (int i=0;i<10;i++)
|
||||
text=text+text;
|
||||
final byte[] data = text.getBytes(StandardCharsets.ISO_8859_1);
|
||||
|
||||
|
||||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
|
@ -436,7 +439,7 @@ public class AsyncIOServletTest
|
|||
{
|
||||
assertScope();
|
||||
response.flushBuffer();
|
||||
|
||||
|
||||
final AsyncContext async = request.startAsync();
|
||||
final ServletOutputStream out = response.getOutputStream();
|
||||
out.setWriteListener(new WriteListener()
|
||||
|
@ -494,18 +497,18 @@ public class AsyncIOServletTest
|
|||
line=in.readLine();
|
||||
assertThat(line, containsString("discontent. How Now Brown Cow. The "));
|
||||
}
|
||||
|
||||
|
||||
if (!latch.await(5, TimeUnit.SECONDS))
|
||||
Assert.fail();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testIsReadyAtEOF() throws Exception
|
||||
{
|
||||
String text = "TEST\n";
|
||||
final byte[] data = text.getBytes(StandardCharsets.ISO_8859_1);
|
||||
|
||||
|
||||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
|
@ -513,17 +516,17 @@ public class AsyncIOServletTest
|
|||
{
|
||||
assertScope();
|
||||
response.flushBuffer();
|
||||
|
||||
|
||||
final AsyncContext async = request.startAsync();
|
||||
final ServletInputStream in = request.getInputStream();
|
||||
final ServletOutputStream out = response.getOutputStream();
|
||||
|
||||
|
||||
in.setReadListener(new ReadListener()
|
||||
{
|
||||
transient int _i=0;
|
||||
transient boolean _minusOne=false;;
|
||||
transient boolean _finished=false;;
|
||||
|
||||
|
||||
@Override
|
||||
public void onError(Throwable t)
|
||||
{
|
||||
|
@ -531,7 +534,7 @@ public class AsyncIOServletTest
|
|||
t.printStackTrace();
|
||||
async.complete();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDataAvailable() throws IOException
|
||||
{
|
||||
|
@ -544,17 +547,17 @@ public class AsyncIOServletTest
|
|||
else if (data[_i++]!=b)
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
|
||||
if (in.isFinished())
|
||||
_finished=true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onAllDataRead() throws IOException
|
||||
{
|
||||
assertScope();
|
||||
out.write(String.format("i=%d eof=%b finished=%b",_i,_minusOne,_finished).getBytes(StandardCharsets.ISO_8859_1));
|
||||
async.complete();
|
||||
async.complete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -584,14 +587,14 @@ public class AsyncIOServletTest
|
|||
assertThat(line, containsString("i="+data.length+" eof=true finished=true"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testOnAllDataRead() throws Exception
|
||||
{
|
||||
String text = "X";
|
||||
final byte[] data = text.getBytes(StandardCharsets.ISO_8859_1);
|
||||
|
||||
|
||||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
|
@ -599,12 +602,12 @@ public class AsyncIOServletTest
|
|||
{
|
||||
assertScope();
|
||||
response.flushBuffer();
|
||||
|
||||
|
||||
final AsyncContext async = request.startAsync();
|
||||
async.setTimeout(5000);
|
||||
final ServletInputStream in = request.getInputStream();
|
||||
final ServletOutputStream out = response.getOutputStream();
|
||||
|
||||
|
||||
in.setReadListener(new ReadListener()
|
||||
{
|
||||
@Override
|
||||
|
@ -614,7 +617,7 @@ public class AsyncIOServletTest
|
|||
t.printStackTrace();
|
||||
async.complete();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDataAvailable() throws IOException
|
||||
{
|
||||
|
@ -636,13 +639,13 @@ public class AsyncIOServletTest
|
|||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onAllDataRead() throws IOException
|
||||
{
|
||||
assertScope();
|
||||
out.write("OK\n".getBytes(StandardCharsets.ISO_8859_1));
|
||||
async.complete();
|
||||
async.complete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -674,13 +677,13 @@ public class AsyncIOServletTest
|
|||
assertThat(line, containsString("OK"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testOtherThreadOnAllDataRead() throws Exception
|
||||
{
|
||||
String text = "X";
|
||||
final byte[] data = text.getBytes(StandardCharsets.ISO_8859_1);
|
||||
|
||||
|
||||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
|
@ -688,15 +691,15 @@ public class AsyncIOServletTest
|
|||
{
|
||||
assertScope();
|
||||
response.flushBuffer();
|
||||
|
||||
|
||||
final AsyncContext async = request.startAsync();
|
||||
async.setTimeout(500000);
|
||||
final ServletInputStream in = request.getInputStream();
|
||||
final ServletOutputStream out = response.getOutputStream();
|
||||
|
||||
|
||||
if (request.getDispatcherType()==DispatcherType.ERROR)
|
||||
throw new IllegalStateException();
|
||||
|
||||
|
||||
in.setReadListener(new ReadListener()
|
||||
{
|
||||
@Override
|
||||
|
@ -706,7 +709,7 @@ public class AsyncIOServletTest
|
|||
t.printStackTrace();
|
||||
async.complete();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDataAvailable() throws IOException
|
||||
{
|
||||
|
@ -736,12 +739,12 @@ public class AsyncIOServletTest
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onAllDataRead() throws IOException
|
||||
{
|
||||
out.write("OK\n".getBytes(StandardCharsets.ISO_8859_1));
|
||||
async.complete();
|
||||
async.complete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -773,7 +776,7 @@ public class AsyncIOServletTest
|
|||
assertThat(line, containsString("OK"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testCompleteBeforeOnAllDataRead() throws Exception
|
||||
|
@ -781,7 +784,7 @@ public class AsyncIOServletTest
|
|||
String text = "XYZ";
|
||||
final byte[] data = text.getBytes(StandardCharsets.ISO_8859_1);
|
||||
final AtomicBoolean allDataRead = new AtomicBoolean(false);
|
||||
|
||||
|
||||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
|
@ -789,11 +792,11 @@ public class AsyncIOServletTest
|
|||
{
|
||||
assertScope();
|
||||
response.flushBuffer();
|
||||
|
||||
|
||||
final AsyncContext async = request.startAsync();
|
||||
final ServletInputStream in = request.getInputStream();
|
||||
final ServletOutputStream out = response.getOutputStream();
|
||||
|
||||
|
||||
in.setReadListener(new ReadListener()
|
||||
{
|
||||
@Override
|
||||
|
@ -802,7 +805,7 @@ public class AsyncIOServletTest
|
|||
assertScope();
|
||||
t.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDataAvailable() throws IOException
|
||||
{
|
||||
|
@ -815,17 +818,17 @@ public class AsyncIOServletTest
|
|||
out.write("OK\n".getBytes(StandardCharsets.ISO_8859_1));
|
||||
async.complete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onAllDataRead() throws IOException
|
||||
{
|
||||
assertScope();
|
||||
out.write("BAD!!!\n".getBytes(StandardCharsets.ISO_8859_1));
|
||||
allDataRead.set(true);
|
||||
throw new IllegalStateException();
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -859,14 +862,14 @@ public class AsyncIOServletTest
|
|||
Assert.assertFalse(allDataRead.get());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testEmptyAsyncRead() throws Exception
|
||||
{
|
||||
final AtomicBoolean oda = new AtomicBoolean();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
|
||||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
|
@ -879,14 +882,14 @@ public class AsyncIOServletTest
|
|||
request.getInputStream().setReadListener(new ReadListener()
|
||||
{
|
||||
@Override
|
||||
public void onDataAvailable() throws IOException
|
||||
public void onDataAvailable() throws IOException
|
||||
{
|
||||
assertScope();
|
||||
oda.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllDataRead() throws IOException
|
||||
public void onAllDataRead() throws IOException
|
||||
{
|
||||
assertScope();
|
||||
asyncContext.complete();
|
||||
|
@ -894,12 +897,12 @@ public class AsyncIOServletTest
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable t)
|
||||
public void onError(Throwable t)
|
||||
{
|
||||
assertScope();
|
||||
t.printStackTrace();
|
||||
asyncContext.complete();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -920,9 +923,102 @@ public class AsyncIOServletTest
|
|||
// wait for onAllDataRead BEFORE closing client
|
||||
latch.await();
|
||||
}
|
||||
|
||||
|
||||
// ODA not called at all!
|
||||
Assert.assertFalse(oda.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteFromOnDataAvailable() throws Exception
|
||||
{
|
||||
Queue<Throwable> errors = new ConcurrentLinkedQueue<>();
|
||||
CountDownLatch writeLatch = new CountDownLatch(1);
|
||||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
AsyncContext asyncContext = request.startAsync();
|
||||
request.getInputStream().setReadListener(new ReadListener()
|
||||
{
|
||||
@Override
|
||||
public void onDataAvailable() throws IOException
|
||||
{
|
||||
ServletInputStream input = request.getInputStream();
|
||||
ServletOutputStream output = response.getOutputStream();
|
||||
while (input.isReady())
|
||||
{
|
||||
byte[] buffer = new byte[512];
|
||||
int read = input.read(buffer);
|
||||
if (read < 0)
|
||||
{
|
||||
asyncContext.complete();
|
||||
break;
|
||||
}
|
||||
if (output.isReady())
|
||||
output.write(buffer, 0, read);
|
||||
else
|
||||
Assert.fail();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllDataRead() throws IOException
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable t)
|
||||
{
|
||||
errors.offer(t);
|
||||
}
|
||||
});
|
||||
response.getOutputStream().setWriteListener(new WriteListener()
|
||||
{
|
||||
@Override
|
||||
public void onWritePossible() throws IOException
|
||||
{
|
||||
writeLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable t)
|
||||
{
|
||||
errors.offer(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
String content = "0123456789ABCDEF";
|
||||
|
||||
try (Socket client = new Socket("localhost", connector.getLocalPort()))
|
||||
{
|
||||
OutputStream output = client.getOutputStream();
|
||||
|
||||
String request = "POST " + path + " HTTP/1.1\r\n" +
|
||||
"Host: localhost:" + connector.getLocalPort() + "\r\n" +
|
||||
"Transfer-Encoding: chunked\r\n" +
|
||||
"\r\n" +
|
||||
"10\r\n" +
|
||||
content + "\r\n";
|
||||
output.write(request.getBytes("UTF-8"));
|
||||
output.flush();
|
||||
|
||||
assertTrue(writeLatch.await(5, TimeUnit.SECONDS));
|
||||
|
||||
request = "" +
|
||||
"0\r\n" +
|
||||
"\r\n";
|
||||
output.write(request.getBytes("UTF-8"));
|
||||
output.flush();
|
||||
|
||||
HttpTester.Input input = HttpTester.from(client.getInputStream());
|
||||
HttpTester.Response response = HttpTester.parseResponse(input);
|
||||
|
||||
assertThat(response.getStatus(), Matchers.equalTo(HttpStatus.OK_200));
|
||||
assertThat(response.getContent(), Matchers.equalTo(content));
|
||||
assertThat(errors, Matchers.hasSize(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue