Merge remote-tracking branch 'origin/jetty-9.3.x' into jetty-9.4.x

This commit is contained in:
Greg Wilkins 2016-08-16 00:11:48 +10:00
commit a90e197633
5 changed files with 105 additions and 13 deletions

View File

@ -718,6 +718,13 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
_written+=BufferUtil.length(content); _written+=BufferUtil.length(content);
sendResponse(null,content,complete,callback); sendResponse(null,content,complete,callback);
} }
@Override
public void resetBuffer()
{
if(isCommitted())
throw new IllegalStateException("Committed");
}
public HttpOutput.Interceptor getNextInterceptor() public HttpOutput.Interceptor getNextInterceptor()
{ {

View File

@ -54,12 +54,66 @@ import org.eclipse.jetty.util.log.Logger;
* close the stream, to be reopened after the inclusion ends.</p> * close the stream, to be reopened after the inclusion ends.</p>
*/ */
public class HttpOutput extends ServletOutputStream implements Runnable public class HttpOutput extends ServletOutputStream implements Runnable
{ {
/**
* The HttpOutput.Inteceptor is a single intercept point for all
* output written to the HttpOutput: via writer; via output stream;
* asynchronously; or blocking.
* <p>
* The Interceptor can be used to implement translations (eg Gzip) or
* additional buffering that acts on all output. Interceptors are
* created in a chain, so that multiple concerns may intercept.
* <p>
* The {@link HttpChannel} is an {@link Interceptor} and is always the
* last link in any Interceptor chain.
* <p>
* Responses are committed by the first call to
* {@link #write(ByteBuffer, boolean, Callback)}
* and closed by a call to {@link #write(ByteBuffer, boolean, Callback)}
* with the last boolean set true. If no content is available to commit
* or close, then a null buffer is passed.
*/
public interface Interceptor public interface Interceptor
{ {
void write(ByteBuffer content, boolean complete, Callback callback); /**
* Write content.
* The response is committed by the first call to write and is closed by
* a call with last == true. Empty content buffers may be passed to
* force a commit or close.
* @param content The content to be written or an empty buffer.
* @param last True if this is the last call to write
* @param callback The callback to use to indicate {@link Callback#succeeded()}
* or {@link Callback#failed(Throwable)}.
*/
void write(ByteBuffer content, boolean last, Callback callback);
/**
* @return The next Interceptor in the chain or null if this is the
* last Interceptor in the chain.
*/
Interceptor getNextInterceptor(); Interceptor getNextInterceptor();
/**
* @return True if the Interceptor is optimized to receive direct
* {@link ByteBuffer}s in the {@link #write(ByteBuffer, boolean, Callback)}
* method. If false is returned, then passing direct buffers may cause
* inefficiencies.
*/
boolean isOptimizedForDirectBuffers(); boolean isOptimizedForDirectBuffers();
/**
* Reset the buffers.
* <p>If the Interceptor contains buffers then reset them.
* @throws IllegalStateException Thrown if the response has been
* committed and buffers and/or headers cannot be reset.
*/
default void resetBuffer() throws IllegalStateException
{
Interceptor next = getNextInterceptor();
if (next!=null)
next.resetBuffer();
};
} }
private static Logger LOG = Log.getLogger(HttpOutput.class); private static Logger LOG = Log.getLogger(HttpOutput.class);
@ -863,15 +917,19 @@ public class HttpOutput extends ServletOutputStream implements Runnable
public void recycle() public void recycle()
{ {
resetBuffer();
_interceptor=_channel; _interceptor=_channel;
if (BufferUtil.hasContent(_aggregate))
BufferUtil.clear(_aggregate);
_written = 0;
reopen();
} }
public void resetBuffer() public void resetBuffer()
{ {
_written = 0; _interceptor.resetBuffer();
if (BufferUtil.hasContent(_aggregate)) if (BufferUtil.hasContent(_aggregate))
BufferUtil.clear(_aggregate); BufferUtil.clear(_aggregate);
_written = 0;
reopen(); reopen();
} }

View File

@ -531,6 +531,9 @@ public class Response implements HttpServletResponse
LOG.debug("Aborting on sendError on committed response {} {}",code,message); LOG.debug("Aborting on sendError on committed response {} {}",code,message);
code=-1; code=-1;
} }
else
resetBuffer();
switch(code) switch(code)
{ {
@ -544,7 +547,7 @@ public class Response implements HttpServletResponse
break; break;
} }
resetBuffer();
_mimeType=null; _mimeType=null;
_characterEncoding=null; _characterEncoding=null;
_outputType = OutputType.NONE; _outputType = OutputType.NONE;
@ -1213,9 +1216,6 @@ public class Response implements HttpServletResponse
@Override @Override
public void resetBuffer() public void resetBuffer()
{ {
if (isCommitted())
throw new IllegalStateException("cannot reset buffer on committed response");
_out.resetBuffer(); _out.resetBuffer();
} }

View File

@ -201,6 +201,14 @@ public class BufferedResponseHandler extends HandlerWrapper
_next=interceptor; _next=interceptor;
_channel=httpChannel; _channel=httpChannel;
} }
@Override
public void resetBuffer()
{
_buffers.clear();
_aggregating=null;
_aggregate=null;
};
@Override @Override
public void write(ByteBuffer content, boolean last, Callback callback) public void write(ByteBuffer content, boolean last, Callback callback)

View File

@ -41,8 +41,6 @@ import org.junit.Test;
/** /**
* Resource Handler test * Resource Handler test
*
* TODO: increase the testing going on here
*/ */
public class BufferedResponseHandlerTest public class BufferedResponseHandlerTest
{ {
@ -95,6 +93,7 @@ public class BufferedResponseHandlerTest
_test._writes=10; _test._writes=10;
_test._flush=false; _test._flush=false;
_test._close=false; _test._close=false;
_test._reset=false;
} }
@Test @Test
@ -218,6 +217,18 @@ public class BufferedResponseHandlerTest
assertThat(response,not(containsString("Write: 1"))); assertThat(response,not(containsString("Write: 1")));
assertThat(response,containsString("Written: true")); assertThat(response,containsString("Written: true"));
} }
@Test
public void testReset() throws Exception
{
_test._reset=true;
String response = _local.getResponse("GET /ctx/include/path HTTP/1.1\r\nHost: localhost\r\n\r\n");
assertThat(response,containsString(" 200 OK"));
assertThat(response,containsString("Write: 0"));
assertThat(response,containsString("Write: 9"));
assertThat(response,containsString("Written: true"));
assertThat(response,not(containsString("RESET")));
}
public static class TestHandler extends AbstractHandler public static class TestHandler extends AbstractHandler
{ {
@ -227,16 +238,25 @@ public class BufferedResponseHandlerTest
int _writes; int _writes;
boolean _flush; boolean _flush;
boolean _close; boolean _close;
boolean _reset;
@Override @Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{ {
baseRequest.setHandled(true); baseRequest.setHandled(true);
if (_bufferSize>0) if (_bufferSize>0)
response.setBufferSize(_bufferSize); response.setBufferSize(_bufferSize);
if (_mimeType!=null) if (_mimeType!=null)
response.setContentType(_mimeType); response.setContentType(_mimeType);
if (_reset)
{
response.getOutputStream().print("THIS WILL BE RESET");
response.getOutputStream().flush();
response.getOutputStream().print("THIS WILL BE RESET");
response.resetBuffer();
}
for (int i=0;i<_writes;i++) for (int i=0;i<_writes;i++)
{ {
response.addHeader("Write",Integer.toString(i)); response.addHeader("Write",Integer.toString(i));
@ -248,7 +268,6 @@ public class BufferedResponseHandlerTest
if (_close) if (_close)
response.getOutputStream().close(); response.getOutputStream().close();
response.addHeader("Written","true"); response.addHeader("Written","true");
} }
} }
} }