Merged branch 'jetty-9.4.x' into 'master'.

This commit is contained in:
Simone Bordet 2016-07-15 18:12:46 +02:00
commit a1cc30c751
6 changed files with 271 additions and 184 deletions

View File

@ -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);

View File

@ -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();

View File

@ -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;
}
}
}

View File

@ -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
*/

View File

@ -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();

View File

@ -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));
}
}
}