Merged branch 'origin/master' into 'jetty-http2'.

This commit is contained in:
Simone Bordet 2014-06-26 11:54:50 +02:00
commit 2608af8f0d
20 changed files with 518 additions and 285 deletions

View File

@ -773,15 +773,14 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
}
@Override
public void failed(Throwable failure)
public void onCompleteFailure(Throwable failure)
{
content.failed(failure);
super.failed(failure);
anyToFailure(failure);
}
@Override
protected void completed()
protected void onCompleteSuccess()
{
// Nothing to do, since we always return false from process().
// Termination is obtained via LastContentCallback.

View File

@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.SSLEngine;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@ -56,6 +57,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Ignore;
import org.junit.Test;
public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
@ -299,6 +301,7 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
}
}
@Ignore
@Slow
@Test
public void testConnectTimeoutFailsRequest() throws Exception
@ -330,6 +333,7 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
Assert.assertNotNull(request.getAbortCause());
}
@Ignore
@Slow
@Test
public void testConnectTimeoutIsCancelledByShorterTimeout() throws Exception

View File

@ -82,7 +82,7 @@ public class Flusher
}
@Override
protected void completed()
protected void onCompleteSuccess()
{
// We never return Action.SUCCEEDED, so this method is never called.
throw new IllegalStateException();
@ -98,7 +98,7 @@ public class Flusher
}
@Override
public void failed(Throwable x)
public void onCompleteFailure(Throwable x)
{
if (active != null)
active.failed(x);
@ -111,8 +111,6 @@ public class Flusher
break;
result.failed(x);
}
super.failed(x);
}
}

View File

@ -713,7 +713,13 @@ public abstract class HTTP2Session implements ISession, Parser.Listener
}
@Override
public void failed(Throwable x)
protected void onCompleteSuccess()
{
throw new IllegalStateException();
}
@Override
protected void onCompleteFailure(Throwable x)
{
LOG.debug(x);
lease.recycle();
@ -723,13 +729,6 @@ public abstract class HTTP2Session implements ISession, Parser.Listener
entry.failed(x);
}
active.clear();
super.failed(x);
}
@Override
protected void completed()
{
throw new IllegalStateException();
}
public void close()

View File

@ -20,13 +20,13 @@ package org.eclipse.jetty.server;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.WritePendingException;
import java.util.concurrent.RejectedExecutionException;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection;
@ -58,7 +58,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
private final HttpParser _parser;
private volatile ByteBuffer _requestBuffer = null;
private volatile ByteBuffer _chunk = null;
private final SendCallback _sendCallback = new SendCallback();
/* ------------------------------------------------------------ */
/** Get the current connection that this thread is dispatched to.
@ -418,54 +418,80 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
fillInterested();
}
@Override
public void onClose()
{
if (_sendCallback.isInUse())
{
LOG.warn("Closed with pending write:"+this);
_sendCallback.failed(new EofException("Connection closed"));
}
super.onClose();
}
@Override
public void run()
{
onFillable();
}
@Override
public void send(ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
{
if (info==null)
new ContentCallback(content,lastContent,callback).iterate();
else
{
// If we are still expecting a 100 continues
if (_channel.isExpecting100Continue())
// then we can't be persistent
_generator.setPersistent(false);
new CommitCallback(info,content,lastContent,callback).iterate();
}
// If we are still expecting a 100 continues when we commit
if (info!=null && _channel.isExpecting100Continue())
// then we can't be persistent
_generator.setPersistent(false);
_sendCallback.reset(info,content,lastContent,callback);
_sendCallback.iterate();
}
@Override
public void send(ByteBuffer content, boolean lastContent, Callback callback)
{
new ContentCallback(content,lastContent,callback).iterate();
_sendCallback.reset(null,content,lastContent,callback);
_sendCallback.iterate();
}
private class CommitCallback extends IteratingCallback
private class SendCallback extends IteratingCallback
{
final ByteBuffer _content;
final boolean _lastContent;
final ResponseInfo _info;
final Callback _callback;
ByteBuffer _header;
private ResponseInfo _info;
private ByteBuffer _content;
private boolean _lastContent;
private Callback _callback;
private ByteBuffer _header;
private boolean _shutdownOut;
CommitCallback(ResponseInfo info, ByteBuffer content, boolean last, Callback callback)
private SendCallback()
{
_info=info;
_content=content;
_lastContent=last;
_callback=callback;
super(true);
}
private void reset(ResponseInfo info, ByteBuffer content, boolean last, Callback callback)
{
if (reset())
{
_info = info;
_content = content;
_lastContent = last;
_callback = callback;
_header = null;
_shutdownOut = false;
}
else
{
callback.failed(new WritePendingException());
}
}
@Override
public Action process() throws Exception
{
if (_callback==null)
throw new IllegalStateException();
ByteBuffer chunk = _chunk;
while (true)
{
@ -548,17 +574,12 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
}
case SHUTDOWN_OUT:
{
getEndPoint().shutdownOutput();
_shutdownOut=true;
continue;
}
case DONE:
{
if (_header!=null)
{
// don't release header in spare content buffer
if (!_lastContent || _content==null || !_content.hasArray() || !_header.hasArray() || _content.array()!=_header.array())
_bufferPool.release(_header);
}
releaseHeader();
return Action.SUCCEEDED;
}
case CONTINUE:
@ -573,113 +594,36 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
}
}
@Override
protected void completed()
private void releaseHeader()
{
ByteBuffer h=_header;
_header=null;
if (h!=null)
_bufferPool.release(h);
}
@Override
protected void onCompleteSuccess()
{
releaseHeader();
_callback.succeeded();
if (_shutdownOut)
getEndPoint().shutdownOutput();
}
@Override
public void failed(final Throwable x)
public void onCompleteFailure(final Throwable x)
{
super.failed(x);
releaseHeader();
failedCallback(_callback,x);
if (_shutdownOut)
getEndPoint().shutdownOutput();
}
}
private class ContentCallback extends IteratingCallback
{
final ByteBuffer _content;
final boolean _lastContent;
final Callback _callback;
ContentCallback(ByteBuffer content, boolean last, Callback callback)
{
_content=content;
_lastContent=last;
_callback=callback;
}
@Override
public Action process() throws Exception
public String toString()
{
ByteBuffer chunk = _chunk;
while (true)
{
HttpGenerator.Result result = _generator.generateResponse(null, null, chunk, _content, _lastContent);
if (LOG.isDebugEnabled())
LOG.debug("{} generate: {} ({},{})@{}",
this,
result,
BufferUtil.toSummaryString(_content),
_lastContent,
_generator.getState());
switch (result)
{
case NEED_HEADER:
throw new IllegalStateException();
case NEED_CHUNK:
{
chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT);
continue;
}
case FLUSH:
{
// Don't write the chunk or the content if this is a HEAD response
if (_channel.getRequest().isHead())
{
BufferUtil.clear(chunk);
BufferUtil.clear(_content);
continue;
}
else if (BufferUtil.hasContent(chunk))
{
if (BufferUtil.hasContent(_content))
getEndPoint().write(this, chunk, _content);
else
getEndPoint().write(this, chunk);
}
else if (BufferUtil.hasContent(_content))
{
getEndPoint().write(this, _content);
}
else
continue;
return Action.SCHEDULED;
}
case SHUTDOWN_OUT:
{
getEndPoint().shutdownOutput();
continue;
}
case DONE:
{
return Action.SUCCEEDED;
}
case CONTINUE:
{
break;
}
default:
{
throw new IllegalStateException("generateResponse="+result);
}
}
}
}
@Override
protected void completed()
{
_callback.succeeded();
}
@Override
public void failed(final Throwable x)
{
super.failed(x);
failedCallback(_callback,x);
return String.format("SendCB@%x{s=%s,i=%s,cb=%s}",hashCode(),getState(),_info,_callback);
}
}
@ -690,5 +634,4 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
// response is bad either with RST or by abnormal completion of chunked response.
getEndPoint().close();
}
}

View File

@ -799,7 +799,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
private abstract class AsyncICB extends IteratingCallback
{
@Override
protected void completed()
protected void onCompleteSuccess()
{
while(true)
{
@ -828,9 +828,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
}
@Override
public void failed(Throwable e)
public void onCompleteFailure(Throwable e)
{
super.failed(e);
_onError=e;
_channel.getState().onWritePossible();
}
@ -950,9 +949,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
}
@Override
protected void completed()
protected void onCompleteSuccess()
{
super.completed();
super.onCompleteSuccess();
if (_complete)
closed();
}
@ -1015,9 +1014,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
}
@Override
public void failed(Throwable x)
public void onCompleteFailure(Throwable x)
{
super.failed(x);
super.onCompleteFailure(x);
_channel.getByteBufferPool().release(_buffer);
try
{
@ -1079,9 +1078,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
}
@Override
public void failed(Throwable x)
public void onCompleteFailure(Throwable x)
{
super.failed(x);
super.onCompleteFailure(x);
_channel.getByteBufferPool().release(_buffer);
try
{
@ -1093,5 +1092,4 @@ public class HttpOutput extends ServletOutputStream implements Runnable
}
}
}
}

View File

@ -72,7 +72,9 @@ public class HttpConnectionTest
connector.setIdleTimeout(500);
server.addConnector(connector);
server.setHandler(new DumpHandler());
server.addBean(new ErrorHandler());
ErrorHandler eh=new ErrorHandler();
eh.setServer(server);
server.addBean(eh);
server.start();
}

View File

@ -124,6 +124,40 @@ public class LocalConnectorTest
assertThat(response,containsString("HTTP/1.1 200 OK"));
assertThat(response,containsString("pathInfo=/R2"));
}
@Test
public void testManyGETs() throws Exception
{
String response=_connector.getResponses(
"GET /R1 HTTP/1.1\r\n"+
"Host: localhost\r\n"+
"\r\n"+
"GET /R2 HTTP/1.1\r\n"+
"Host: localhost\r\n"+
"\r\n"+
"GET /R3 HTTP/1.1\r\n"+
"Host: localhost\r\n"+
"\r\n"+
"GET /R4 HTTP/1.1\r\n"+
"Host: localhost\r\n"+
"\r\n"+
"GET /R5 HTTP/1.1\r\n"+
"Host: localhost\r\n"+
"\r\n"+
"GET /R6 HTTP/1.1\r\n"+
"Host: localhost\r\n"+
"Connection: close\r\n"+
"\r\n");
String r=response;
for (int i=1;i<=6;i++)
{
assertThat(r,containsString("HTTP/1.1 200 OK"));
assertThat(r,containsString("pathInfo=/R"+i));
r=r.substring(r.indexOf("</html>")+8);
}
}
@Test
public void testGETandGET() throws Exception

View File

@ -18,18 +18,6 @@
package org.eclipse.jetty.server;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
@ -43,7 +31,6 @@ import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
@ -68,6 +55,16 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class RequestTest
{
private static final Logger LOG = Log.getLogger(RequestTest.class);
@ -130,6 +127,41 @@ public class RequestTest
}
@Test
public void testEmptyHeaders() throws Exception
{
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
assertNotNull(request.getLocale());
assertTrue(request.getLocales().hasMoreElements());
assertNull(request.getContentType());
assertNull(request.getCharacterEncoding());
assertEquals(0,request.getQueryString().length());
assertEquals(-1,request.getContentLength());
assertNull(request.getCookies());
assertNull(request.getHeader("Name"));
assertFalse(request.getHeaders("Name").hasMoreElements());
assertEquals(-1,request.getDateHeader("Name"));
return true;
}
};
String request="GET /? HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Connection: close\n"+
"Content-Type: \n"+
"Accept-Language: \n"+
"Cookie: \n"+
"Name: \n"+
"\n";
String responses=_connector.getResponses(request);
assertTrue(responses.startsWith("HTTP/1.1 200"));
}
@Test
public void testMultiPartNoConfig() throws Exception
{
@ -286,13 +318,13 @@ public class RequestTest
"\n";
String responses=_connector.getResponses(request);
assertThat(responses,Matchers.startsWith("HTTP/1.1 400"));
assertThat(responses, Matchers.startsWith("HTTP/1.1 400"));
}
@Test
public void testContentTypeEncoding() throws Exception
{
final ArrayList<String> results = new ArrayList<String>();
final ArrayList<String> results = new ArrayList<>();
_handler._checker = new RequestTester()
{
@Override
@ -344,7 +376,7 @@ public class RequestTest
@Test
public void testHostPort() throws Exception
{
final ArrayList<String> results = new ArrayList<String>();
final ArrayList<String> results = new ArrayList<>();
_handler._checker = new RequestTester()
{
@Override
@ -365,7 +397,7 @@ public class RequestTest
"Connection: close\n"+
"\n");
int i=0;
assertThat(response,Matchers.containsString("200 OK"));
assertThat(response, Matchers.containsString("200 OK"));
assertEquals("http://myhost/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
@ -379,7 +411,7 @@ public class RequestTest
"Connection: close\n"+
"\n");
i=0;
assertThat(response,Matchers.containsString("200 OK"));
assertThat(response, Matchers.containsString("200 OK"));
assertEquals("http://myhost:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
@ -391,7 +423,7 @@ public class RequestTest
"GET http://myhost:8888/ HTTP/1.0\n"+
"\n");
i=0;
assertThat(response,Matchers.containsString("200 OK"));
assertThat(response, Matchers.containsString("200 OK"));
assertEquals("http://myhost:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
@ -404,7 +436,7 @@ public class RequestTest
"Connection: close\n"+
"\n");
i=0;
assertThat(response,Matchers.containsString("200 OK"));
assertThat(response, Matchers.containsString("200 OK"));
assertEquals("http://myhost:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
@ -419,7 +451,7 @@ public class RequestTest
"\n");
i=0;
assertThat(response,Matchers.containsString("200 OK"));
assertThat(response, Matchers.containsString("200 OK"));
assertEquals("http://1.2.3.4/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("1.2.3.4",results.get(i++));
@ -433,7 +465,7 @@ public class RequestTest
"Connection: close\n"+
"\n");
i=0;
assertThat(response,Matchers.containsString("200 OK"));
assertThat(response, Matchers.containsString("200 OK"));
assertEquals("http://1.2.3.4:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("1.2.3.4",results.get(i++));
@ -447,7 +479,7 @@ public class RequestTest
"Connection: close\n"+
"\n");
i=0;
assertThat(response,Matchers.containsString("200 OK"));
assertThat(response, Matchers.containsString("200 OK"));
assertEquals("http://[::1]/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("::1",results.get(i++));
@ -461,7 +493,7 @@ public class RequestTest
"Connection: close\n"+
"\n");
i=0;
assertThat(response,Matchers.containsString("200 OK"));
assertThat(response, Matchers.containsString("200 OK"));
assertEquals("http://[::1]:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("::1",results.get(i++));
@ -477,7 +509,7 @@ public class RequestTest
"Connection: close\n"+
"\n");
i=0;
assertThat(response,Matchers.containsString("200 OK"));
assertThat(response, Matchers.containsString("200 OK"));
assertEquals("https://[::1]/",results.get(i++));
assertEquals("remote",results.get(i++));
assertEquals("::1",results.get(i++));
@ -493,7 +525,7 @@ public class RequestTest
"x-forwarded-proto: https\n"+
"\n");
i=0;
assertThat(response,Matchers.containsString("200 OK"));
assertThat(response, Matchers.containsString("200 OK"));
assertEquals("https://[::1]:8888/",results.get(i++));
assertEquals("remote",results.get(i++));
assertEquals("::1",results.get(i++));
@ -541,7 +573,7 @@ public class RequestTest
Log.getRootLogger().debug("test l={}",l);
String response = _connector.getResponses(request);
Log.getRootLogger().debug(response);
assertThat(response,Matchers.containsString(" 200 OK"));
assertThat(response, Matchers.containsString(" 200 OK"));
assertEquals(l,length.get());
content+="x";
}
@ -781,9 +813,9 @@ public class RequestTest
"\n",
200, TimeUnit.MILLISECONDS
);
assertThat(response,containsString("200"));
assertThat(response,Matchers.not(containsString("Connection: close")));
assertThat(response,containsString("Hello World"));
assertThat(response, Matchers.containsString("200"));
assertThat(response, Matchers.not(Matchers.containsString("Connection: close")));
assertThat(response, Matchers.containsString("Hello World"));
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
@ -791,9 +823,9 @@ public class RequestTest
"Connection: close\n"+
"\n"
);
assertThat(response,containsString("200"));
assertThat(response,containsString("Connection: close"));
assertThat(response,containsString("Hello World"));
assertThat(response, Matchers.containsString("200"));
assertThat(response, Matchers.containsString("Connection: close"));
assertThat(response, Matchers.containsString("Hello World"));
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
@ -802,18 +834,18 @@ public class RequestTest
"\n"
);
assertThat(response,containsString("200"));
assertThat(response,containsString("Connection: close"));
assertThat(response,containsString("Hello World"));
assertThat(response, Matchers.containsString("200"));
assertThat(response, Matchers.containsString("Connection: close"));
assertThat(response, Matchers.containsString("Hello World"));
response=_connector.getResponses(
"GET / HTTP/1.0\n"+
"Host: whatever\n"+
"\n"
);
assertThat(response,containsString("200"));
assertThat(response,not(containsString("Connection: close")));
assertThat(response,containsString("Hello World"));
assertThat(response, Matchers.containsString("200"));
assertThat(response, Matchers.not(Matchers.containsString("Connection: close")));
assertThat(response, Matchers.containsString("Hello World"));
response=_connector.getResponses(
"GET / HTTP/1.0\n"+
@ -821,8 +853,8 @@ public class RequestTest
"Connection: Other, close\n"+
"\n"
);
assertThat(response,containsString("200"));
assertThat(response,containsString("Hello World"));
assertThat(response, Matchers.containsString("200"));
assertThat(response, Matchers.containsString("Hello World"));
response=_connector.getResponses(
"GET / HTTP/1.0\n"+
@ -831,9 +863,9 @@ public class RequestTest
"\n",
200, TimeUnit.MILLISECONDS
);
assertThat(response,containsString("200"));
assertThat(response,Matchers.containsString("Connection: keep-alive"));
assertThat(response,containsString("Hello World"));
assertThat(response, Matchers.containsString("200"));
assertThat(response, Matchers.containsString("Connection: keep-alive"));
assertThat(response, Matchers.containsString("Hello World"));
_handler._checker = new RequestTester()
{
@ -853,9 +885,9 @@ public class RequestTest
"\n",
200, TimeUnit.MILLISECONDS
);
assertThat(response,containsString("200"));
assertThat(response,containsString("Connection: TE,Other"));
assertThat(response,containsString("Hello World"));
assertThat(response, Matchers.containsString("200"));
assertThat(response, Matchers.containsString("Connection: TE,Other"));
assertThat(response, Matchers.containsString("Hello World"));
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
@ -863,15 +895,15 @@ public class RequestTest
"Connection: close\n"+
"\n"
);
assertThat(response,Matchers.containsString("200 OK"));
assertThat(response,Matchers.containsString("Connection: close"));
assertThat(response,Matchers.containsString("Hello World"));
assertThat(response, Matchers.containsString("200 OK"));
assertThat(response, Matchers.containsString("Connection: close"));
assertThat(response, Matchers.containsString("Hello World"));
}
@Test
public void testCookies() throws Exception
{
final ArrayList<Cookie> cookies = new ArrayList<Cookie>();
final ArrayList<Cookie> cookies = new ArrayList<>();
_handler._checker = new RequestTester()
{
@ -927,7 +959,6 @@ public class RequestTest
assertEquals("other", cookies.get(1).getName());
assertEquals("quoted=;value", cookies.get(1).getValue());
cookies.clear();
response=_connector.getResponses(
"GET /other HTTP/1.1\n"+
@ -942,7 +973,8 @@ public class RequestTest
"Connection: close\n"+
"\n"
);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
assertThat(response.substring(15), Matchers.containsString("HTTP/1.1 200 OK"));
assertEquals(4,cookies.size());
assertEquals("name", cookies.get(0).getName());
assertEquals("value", cookies.get(0).getValue());
@ -966,7 +998,8 @@ public class RequestTest
"Connection: close\n"+
"\n"
);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
assertThat(response.substring(15), Matchers.containsString("HTTP/1.1 200 OK"));
assertEquals(4,cookies.size());
assertEquals("name", cookies.get(0).getName());
assertEquals("value", cookies.get(0).getValue());
@ -1025,6 +1058,78 @@ public class RequestTest
}
@Test
public void testCookieLeak() throws Exception
{
final String[] cookie=new String[10];
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
for (int i=0;i<cookie.length; i++)
cookie[i]=null;
Cookie[] cookies = request.getCookies();
for (int i=0;cookies!=null && i<cookies.length; i++)
{
cookie[i]=cookies[i].getValue();
}
return true;
}
};
String request="POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Cookie: other=cookie\r\n"+
"\r\n"
+
"POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Cookie: name=value\r\n"+
"Connection: close\r\n"+
"\r\n";
_connector.getResponses(request);
assertEquals("value",cookie[0]);
assertEquals(null,cookie[1]);
request="POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Cookie: name=value\r\n"+
"\r\n"
+
"POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Cookie:\r\n"+
"Connection: close\r\n"+
"\r\n";
_connector.getResponses(request);
assertEquals(null,cookie[0]);
assertEquals(null,cookie[1]);
request="POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Cookie: name=value\r\n"+
"Cookie: other=cookie\r\n"+
"\r\n"
+
"POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Cookie: name=value\r\n"+
"Cookie:\r\n"+
"Connection: close\r\n"+
"\r\n";
_connector.getResponses(request);
assertEquals("value",cookie[0]);
assertEquals(null,cookie[1]);
}
@Test
public void testHashDOS() throws Exception

View File

@ -218,7 +218,7 @@ public class Flusher
}
@Override
protected void completed()
protected void onCompleteSuccess()
{
// will never be called as process always returns SCHEDULED or IDLE
throw new IllegalStateException();
@ -242,7 +242,7 @@ public class Flusher
}
@Override
public void failed(Throwable x)
public void onCompleteFailure(Throwable x)
{
List<StandardSession.FrameBytes> failed = new ArrayList<>();
synchronized (lock)
@ -261,7 +261,6 @@ public class Flusher
// Notify outside the synchronized block.
for (StandardSession.FrameBytes frame : failed)
frame.failed(x);
super.failed(x);
}
}
}

View File

@ -71,14 +71,25 @@ public abstract class IteratingCallback implements Callback
/**
* Indicates that {@link #process()} has completed the overall job.
*/
SUCCEEDED,
/**
* Indicates that {@link #process()} has failed the overall job.
*/
FAILED
SUCCEEDED
}
private final AtomicReference<State> _state = new AtomicReference<>(State.INACTIVE);
private final AtomicReference<State> _state;
protected IteratingCallback()
{
_state = new AtomicReference<>(State.INACTIVE);
}
protected IteratingCallback(boolean needReset)
{
_state = new AtomicReference<>(needReset?State.SUCCEEDED:State.INACTIVE);
}
protected State getState()
{
return _state.get();
}
/**
* Method called by {@link #iterate()} to process the sub task.
@ -91,7 +102,6 @@ public abstract class IteratingCallback implements Callback
* <li>{@link Action#SCHEDULED} when the sub task asynchronous execution
* has been started</li>
* <li>{@link Action#SUCCEEDED} when the overall job is completed</li>
* <li>{@link Action#FAILED} when the overall job cannot be completed</li>
* </ul>
*
* @throws Exception if the sub task processing throws
@ -101,7 +111,12 @@ public abstract class IteratingCallback implements Callback
/**
* Invoked when the overall task has completed successfully.
*/
protected abstract void completed();
protected abstract void onCompleteSuccess();
/**
* Invoked when the overall task has completely failed.
*/
protected abstract void onCompleteFailure(Throwable x);
/**
* This method must be invoked by applications to start the processing
@ -196,13 +211,7 @@ public abstract class IteratingCallback implements Callback
case SUCCEEDED:
{
// The overall job has completed.
if (completeSuccess())
completed();
return true;
}
case FAILED:
{
completeFailure();
completeSuccess();
return true;
}
default:
@ -265,43 +274,49 @@ public abstract class IteratingCallback implements Callback
* {@code super.failed(Throwable)}.
*/
@Override
public void failed(Throwable x)
public final void failed(Throwable x)
{
completeFailure();
completeFailure(x);
}
private boolean completeSuccess()
protected void completeSuccess()
{
while (true)
{
State current = _state.get();
if (current == State.FAILED)
switch(current)
{
// Success arrived too late, sorry.
return false;
}
else
{
if (_state.compareAndSet(current, State.SUCCEEDED))
return true;
case SUCCEEDED:
case FAILED:
// Already complete!.
return;
default:
if (_state.compareAndSet(current, State.SUCCEEDED))
{
onCompleteSuccess();
return;
}
}
}
}
private void completeFailure()
protected void completeFailure(Throwable x)
{
while (true)
{
State current = _state.get();
if (current == State.SUCCEEDED)
switch(current)
{
// Failed arrived too late, sorry.
return;
}
else
{
if (_state.compareAndSet(current, State.FAILED))
break;
case SUCCEEDED:
case FAILED:
// Already complete!.
return;
default:
if (_state.compareAndSet(current, State.FAILED))
{
onCompleteFailure(x);
return;
}
}
}
}
@ -313,6 +328,23 @@ public abstract class IteratingCallback implements Callback
{
return _state.get() == State.INACTIVE;
}
/* ------------------------------------------------------------ */
/**
* @return true if the callback is not INACTIVE, FAILED or SUCCEEDED.
*/
public boolean isInUse()
{
switch(_state.get())
{
case INACTIVE:
case FAILED:
case SUCCEEDED:
return false;
default:
return true;
}
}
/**
* @return whether this callback has failed
@ -330,12 +362,42 @@ public abstract class IteratingCallback implements Callback
return _state.get() == State.SUCCEEDED;
}
/* ------------------------------------------------------------ */
/** Reset the callback
* <p>A callback can only be reset to INACTIVE from the SUCCEEDED or FAILED states or if it is already INACTIVE.
* @return True if the reset was successful
*/
public boolean reset()
{
while (true)
{
switch(_state.get())
{
case INACTIVE:
return true;
case SUCCEEDED:
if (_state.compareAndSet(State.SUCCEEDED, State.INACTIVE))
return true;
break;
case FAILED:
if (_state.compareAndSet(State.FAILED, State.INACTIVE))
return true;
break;
default:
return false;
}
}
}
@Override
public String toString()
{
return String.format("%s[%s]", super.toString(), _state);
}
/**
* The internal states of this callback
*/

View File

@ -48,15 +48,14 @@ public abstract class IteratingNestedCallback extends IteratingCallback
}
@Override
protected void completed()
protected void onCompleteSuccess()
{
_callback.succeeded();
}
@Override
public void failed(Throwable x)
protected void onCompleteFailure(Throwable x)
{
super.failed(x);
_callback.failed(x);
}

View File

@ -27,6 +27,7 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.DirectoryIteratorException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
@ -264,32 +265,54 @@ public class PathResource extends Resource
}
}
@Override
public URI getAlias()
{
if (Files.isSymbolicLink(path))
{
try
{
return path.toRealPath().toUri();
}
catch (IOException e)
{
LOG.debug(e);
return null;
}
}
else
{
return null;
}
}
@Override
public String[] list()
{
try
try (DirectoryStream<Path> dir = Files.newDirectoryStream(path))
{
List<String> entries = new ArrayList<>();
try (DirectoryStream<Path> dir = Files.newDirectoryStream(path))
for (Path entry : dir)
{
for (Path entry : dir)
String name = entry.getFileName().toString();
if (Files.isDirectory(entry))
{
String name = entry.getFileName().toString();
if (Files.isDirectory(entry))
{
name += "/";
}
entries.add(name);
name += "/";
}
entries.add(name);
}
int size = entries.size();
return entries.toArray(new String[size]);
}
catch (DirectoryIteratorException e)
{
LOG.debug(e);
}
catch (IOException e)
{
LOG.ignore(e);
LOG.debug(e);
}
return null;
}

View File

@ -262,16 +262,14 @@ public class IteratingCallbackTest
int processed=0;
@Override
protected void completed()
protected void onCompleteSuccess()
{
completed.countDown();
}
@Override
public void failed(Throwable x)
public void onCompleteFailure(Throwable x)
{
super.failed(x);
completed.countDown();
}

View File

@ -33,7 +33,10 @@ import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.FileSystemException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -84,7 +87,7 @@ public abstract class AbstractFSResourceTest
}
else if (OS.IS_WINDOWS)
{
newResource(new URI("file:/dev/null"));
newResource(new URI("file://some\"text\""));
}
}
@ -323,12 +326,57 @@ public abstract class AbstractFSResourceTest
expected,actual);
}
}
@Test
public void testSymlink() throws Exception
{
File dir = testdir.getDir();
Path foo = new File(dir, "foo").toPath();
Path bar = new File(dir, "bar").toPath();
try
{
Files.createFile(foo);
Files.createSymbolicLink(bar,foo);
}
catch (UnsupportedOperationException | FileSystemException e)
{
// if unable to create symlink, no point testing the rest
// this is the path that Microsoft Windows takes.
assumeNoException(e);
}
try (Resource base = newResource(testdir.getDir()))
{
Resource resFoo = base.addPath("foo");
Resource resBar = base.addPath("bar");
// Access to the same resource, but via a symlink means that they are not equivalent
assertThat("foo.equals(bar)", resFoo.equals(resBar), is(false));
assertThat("foo.alias", resFoo.getAlias(), nullValue());
assertThat("bar.alias", resBar.getAlias(), is(foo.toUri()));
}
}
@Test
public void testSemicolon() throws Exception
{
assumeTrue(!OS.IS_WINDOWS);
createEmptyFile("foo;");
File dir = testdir.getDir();
try
{
// attempt to create file
Path foo = new File(dir, "foo;").toPath();
Files.createFile(foo);
}
catch (Exception e)
{
// if unable to create file, no point testing the rest
// this is the path that Microsoft Windows takes.
assumeNoException(e);
}
try (Resource base = newResource(testdir.getDir()))
{

View File

@ -386,11 +386,19 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames
}
@Override
protected void completed()
protected void onCompleteSuccess()
{
// This IteratingCallback never completes.
}
@Override
protected void onCompleteFailure(Throwable x)
{
// This IteratingCallback never fails.
// The callback are those provided by WriteCallback (implemented
// below) and even in case of writeFailed() we call succeeded().
}
@Override
public void writeSuccess()
{

View File

@ -368,10 +368,19 @@ public abstract class CompressExtension extends AbstractExtension
}
@Override
protected void completed()
protected void onCompleteSuccess()
{
// This IteratingCallback never completes.
}
@Override
protected void onCompleteFailure(Throwable x)
{
// Fail all the frames in the queue.
FrameEntry entry;
while ((entry = entries.poll()) != null)
notifyCallbackFailure(entry.callback, x);
}
@Override
public void writeSuccess()
@ -388,10 +397,6 @@ public abstract class CompressExtension extends AbstractExtension
// If something went wrong, very likely the compression context
// will be invalid, so we need to fail this IteratingCallback.
failed(x);
// Now no more frames can be queued, fail those in the queue.
FrameEntry entry;
while ((entry = entries.poll()) != null)
notifyCallbackFailure(entry.callback, x);
}
}
}

View File

@ -150,11 +150,19 @@ public class FragmentExtension extends AbstractExtension
}
@Override
protected void completed()
protected void onCompleteSuccess()
{
// This IteratingCallback never completes.
}
@Override
protected void onCompleteFailure(Throwable x)
{
// This IteratingCallback never fails.
// The callback are those provided by WriteCallback (implemented
// below) and even in case of writeFailed() we call succeeded().
}
@Override
public void writeSuccess()
{

View File

@ -91,13 +91,13 @@ public class FrameFlusher
}
@Override
protected void completed()
protected void onCompleteSuccess()
{
// This IteratingCallback never completes.
}
@Override
public void failed(Throwable x)
public void onCompleteFailure(Throwable x)
{
for (FrameEntry entry : entries)
{
@ -105,7 +105,6 @@ public class FrameFlusher
entry.release();
}
entries.clear();
super.failed(x);
failure = x;
onFailure(x);
}

View File

@ -258,9 +258,11 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.17</version>
<configuration>
<argLine>-showversion -Xmx1g -Xms1g -XX:+PrintGCDetails</argLine>
<failIfNoTests>false</failIfNoTests>
<runMode>random</runMode>
<systemProperties>
<!--
<property>