Merge branch 'master' into jetty-9.4.x-Feature

This commit is contained in:
Greg Wilkins 2015-12-03 14:20:52 +11:00
commit 120e33034f
13 changed files with 400 additions and 23 deletions

View File

@ -138,18 +138,24 @@ public abstract class ChannelEndPoint extends AbstractEndPoint implements Manage
LOG.debug("doClose {}", this);
try
{
try
{
_channel.close();
}
catch (IOException e)
{
LOG.debug(e);
}
finally
{
super.doClose();
}
_channel.close();
}
catch (IOException e)
{
LOG.debug(e);
}
finally
{
super.doClose();
}
}
@Override
public void onClose()
{
try
{
super.onClose();
}
finally
{
@ -157,6 +163,7 @@ public abstract class ChannelEndPoint extends AbstractEndPoint implements Manage
_selector.onClose(this);
}
}
@Override
public int fill(ByteBuffer buffer) throws IOException

View File

@ -131,6 +131,8 @@ public abstract class FillInterest
public void onClose()
{
Callback callback = _interested.get();
if (LOG.isDebugEnabled())
LOG.debug("{} onClose {}",this,callback);
if (callback != null && _interested.compareAndSet(callback, null))
callback.failed(new ClosedChannelException());
}

View File

@ -297,7 +297,6 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
*/
protected void endPointClosed(EndPoint endpoint)
{
endpoint.onClose();
}
/**

View File

@ -82,7 +82,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
setWriteListener() READY->owp ise ise ise ise ise
write() OPEN ise PENDING wpe wpe eof
flush() OPEN ise PENDING wpe wpe eof
close() CLOSED CLOSED CLOSED CLOSED wpe CLOSED
close() CLOSED CLOSED CLOSED CLOSED CLOSED CLOSED
isReady() OPEN:true READY:true READY:true UNREADY:false UNREADY:false CLOSED:true
write completed - - - ASYNC READY->owp -
*/
@ -195,11 +195,17 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
return;
}
case ASYNC:
case UNREADY:
case PENDING:
{
if (_state.compareAndSet(state,OutputState.ERROR))
_writeListener.onError(_onError==null?new EofException("Async close"):_onError);
break;
if (!_state.compareAndSet(state,OutputState.CLOSED))
break;
IOException ex = new IOException("Closed while Pending/Unready");
LOG.warn(ex.toString());
LOG.debug(ex);
_channel.abort(ex);
}
default:
{
@ -321,6 +327,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
return;
case PENDING:
return;
case UNREADY:
throw new WritePendingException();

View File

@ -29,6 +29,9 @@ import java.io.PrintWriter;
import java.net.Socket;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@ -47,6 +50,8 @@ import org.junit.Rule;
public abstract class AbstractHttpTest
{
private final static Set<String> __noBodyCodes = new HashSet<>(Arrays.asList(new String[]{"100","101","102","204","304"}));
@Rule
public TestTracker tracker = new TestTracker();
@ -93,8 +98,10 @@ public abstract class AbstractHttpTest
writer.flush();
SimpleHttpResponse response = httpParser.readResponse(reader);
if ("HTTP/1.1".equals(httpVersion) && response.getHeaders().get("content-length") == null && response
.getHeaders().get("transfer-encoding") == null)
if ("HTTP/1.1".equals(httpVersion)
&& response.getHeaders().get("content-length") == null
&& response.getHeaders().get("transfer-encoding") == null
&& !__noBodyCodes.contains(response.getCode()))
assertThat("If HTTP/1.1 response doesn't contain transfer-encoding or content-length headers, " +
"it should contain connection:close", response.getHeaders().get("connection"), is("close"));
return response;

View File

@ -30,6 +30,7 @@ import static org.junit.Assert.assertTrue;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@ -1536,6 +1537,71 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
}
}
@Test
public void testWriteBodyAfterNoBodyResponse() throws Exception
{
configureServer(new WriteBodyAfterNoBodyResponseHandler());
Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
final OutputStream out=client.getOutputStream();
out.write("GET / HTTP/1.1\r\nHost: test\r\n\r\n".getBytes());
out.write("GET / HTTP/1.1\r\nHost: test\r\nConnection: close\r\n\r\n".getBytes());
out.flush();
BufferedReader in =new BufferedReader(new InputStreamReader(client.getInputStream()));
String line=in.readLine();
assertThat(line, containsString(" 304 "));
while (true)
{
line=in.readLine();
if (line==null)
throw new EOFException();
if (line.length()==0)
break;
assertThat(line, not(containsString("Content-Length")));
assertThat(line, not(containsString("Content-Type")));
assertThat(line, not(containsString("Transfer-Encoding")));
}
line=in.readLine();
assertThat(line, containsString(" 304 "));
while (true)
{
line=in.readLine();
if (line==null)
throw new EOFException();
if (line.length()==0)
break;
assertThat(line, not(containsString("Content-Length")));
assertThat(line, not(containsString("Content-Type")));
assertThat(line, not(containsString("Transfer-Encoding")));
}
do
{
line=in.readLine();
}
while (line!=null);
}
private class WriteBodyAfterNoBodyResponseHandler extends AbstractHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setStatus(304);
response.getOutputStream().print("yuck");
response.flushBuffer();
}
}
public class NoopHandler extends AbstractHandler
{
@Override

View File

@ -62,6 +62,7 @@ import org.eclipse.jetty.server.handler.gzip.GzipHandler;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.BaseHolder.Source;
import org.eclipse.jetty.util.DecoratedObjectFactory;
import org.eclipse.jetty.util.DeprecationWarning;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.LifeCycle;
@ -90,7 +91,7 @@ public class ServletContextHandler extends ContextHandler
public interface ServletContainerInitializerCaller extends LifeCycle {};
protected final DecoratedObjectFactory _objFactory = new DecoratedObjectFactory();
protected final DecoratedObjectFactory _objFactory;
protected Class<? extends SecurityHandler> _defaultSecurityHandlerClass=org.eclipse.jetty.security.ConstraintSecurityHandler.class;
protected SessionHandler _sessionHandler;
protected SecurityHandler _securityHandler;
@ -150,6 +151,9 @@ public class ServletContextHandler extends ContextHandler
_sessionHandler = sessionHandler;
_securityHandler = securityHandler;
_servletHandler = servletHandler;
_objFactory = new DecoratedObjectFactory();
_objFactory.addDecorator(new DeprecationWarning());
if (contextPath!=null)
setContextPath(contextPath);

View File

@ -18,9 +18,14 @@
package org.eclipse.jetty.servlet;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.BufferedReader;
import java.io.IOException;
@ -57,6 +62,7 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -69,6 +75,7 @@ public class AsyncServletIOTest
protected AsyncIOServlet _servlet0=new AsyncIOServlet();
protected AsyncIOServlet2 _servlet2=new AsyncIOServlet2();
protected AsyncIOServlet3 _servlet3=new AsyncIOServlet3();
protected AsyncIOServlet4 _servlet4=new AsyncIOServlet4();
protected int _port;
protected Server _server = new Server();
protected ServletHandler _servletHandler;
@ -101,6 +108,10 @@ public class AsyncServletIOTest
holder3.setAsyncSupported(true);
_servletHandler.addServletWithMapping(holder3,"/path3/*");
ServletHolder holder4=new ServletHolder(_servlet4);
holder4.setAsyncSupported(true);
_servletHandler.addServletWithMapping(holder4,"/path4/*");
_server.start();
_port=_connector.getLocalPort();
@ -232,7 +243,7 @@ public class AsyncServletIOTest
int port=_port;
try (Socket socket = new Socket("localhost",port))
{
socket.setSoTimeout(1000000);
socket.setSoTimeout(10000);
OutputStream out = socket.getOutputStream();
out.write(request.toString().getBytes(StandardCharsets.ISO_8859_1));
@ -263,6 +274,8 @@ public class AsyncServletIOTest
}
}
public synchronized List<String> process(String content,int... writes) throws Exception
{
return process(content.getBytes(StandardCharsets.ISO_8859_1),writes);
@ -596,4 +609,180 @@ public class AsyncServletIOTest
async.complete();
}
}
@Test
public void testCompleteWhilePending() throws Exception
{
StringBuilder request = new StringBuilder(512);
request.append("POST /ctx/path4/info HTTP/1.1\r\n")
.append("Host: localhost\r\n")
.append("Content-Type: text/plain\r\n")
.append("Content-Length: 20\r\n")
.append("\r\n")
.append("12345678\r\n");
int port=_port;
List<String> list = new ArrayList<>();
try (Socket socket = new Socket("localhost",port))
{
socket.setSoTimeout(10000);
OutputStream out = socket.getOutputStream();
out.write(request.toString().getBytes(ISO_8859_1));
out.flush();
Thread.sleep(100);
out.write("ABC".getBytes(ISO_8859_1));
out.flush();
Thread.sleep(100);
out.write("DEF".getBytes(ISO_8859_1));
out.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// response line
String line = in.readLine();
LOG.debug("response-line: "+line);
Assert.assertThat(line,startsWith("HTTP/1.1 200 OK"));
boolean chunked=false;
// Skip headers
while (line!=null)
{
line = in.readLine();
LOG.debug("header-line: "+line);
chunked|="Transfer-Encoding: chunked".equals(line);
if (line.length()==0)
break;
}
assertTrue(chunked);
// Get body slowly
String last=null;
try
{
while (true)
{
last=line;
//Thread.sleep(1000);
line = in.readLine();
LOG.debug("body: "+line);
if (line==null)
break;
list.add(line);
}
}
catch(IOException e)
{
}
LOG.debug("last: "+last);
// last non empty line should contain some X's
assertThat(last,containsString("X"));
// last non empty line should not contain end chunk
assertThat(last,not(containsString("0")));
}
assertTrue(_servlet4.completed.await(5, TimeUnit.SECONDS));
Thread.sleep(100);
assertEquals(2,_servlet4.onDA.get());
assertEquals(2,_servlet4.onWP.get());
}
@SuppressWarnings("serial")
public class AsyncIOServlet4 extends HttpServlet
{
public CountDownLatch completed = new CountDownLatch(1);
public AtomicInteger onDA = new AtomicInteger();
public AtomicInteger onWP = new AtomicInteger();
@Override
public void doPost(final HttpServletRequest request, final HttpServletResponse response) throws IOException
{
final AsyncContext async = request.startAsync();
final ServletInputStream in = request.getInputStream();
final ServletOutputStream out = response.getOutputStream();
in.setReadListener(new ReadListener()
{
@Override
public void onError(Throwable t)
{
t.printStackTrace();
}
@Override
public void onDataAvailable() throws IOException
{
onDA.incrementAndGet();
if (onDA.get()>2)
return;
// Read all available content
while(in.isReady())
if (in.read()<0)
throw new IllegalStateException();
if (onDA.get()==1)
return;
final byte[] buffer = new byte[64*1024];
Arrays.fill(buffer,(byte)'X');
for (int i=199;i<buffer.length;i+=200)
buffer[i]=(byte)'\n';
// Once we read block, let's make ourselves write blocked
out.setWriteListener(new WriteListener()
{
@Override
public void onWritePossible() throws IOException
{
onWP.incrementAndGet();
if (onWP.get()>2)
return;
while (out.isReady())
out.write(buffer);
if (onWP.get()==1)
return;
try
{
// As soon as we are write blocked, complete
async.complete();
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
completed.countDown();
}
}
@Override
public void onError(Throwable t)
{
t.printStackTrace();
}
});
}
@Override
public void onAllDataRead() throws IOException
{
throw new IllegalStateException();
}
});
}
}
}

View File

@ -387,7 +387,7 @@ public class ServletContextHandlerTest
String expected = String.format("Attribute[%s] = %s", DecoratedObjectFactory.ATTR, DecoratedObjectFactory.class.getName());
assertThat("Has context attribute", response, containsString(expected));
assertThat("Decorators size", response, containsString("Decorators.size = [1]"));
assertThat("Decorators size", response, containsString("Decorators.size = [2]"));
expected = String.format("decorator[] = %s", DummyLegacyDecorator.class.getName());
assertThat("Specific Legacy Decorator", response, containsString(expected));
@ -414,7 +414,7 @@ public class ServletContextHandlerTest
String expected = String.format("Attribute[%s] = %s", DecoratedObjectFactory.ATTR, DecoratedObjectFactory.class.getName());
assertThat("Has context attribute", response, containsString(expected));
assertThat("Decorators size", response, containsString("Decorators.size = [1]"));
assertThat("Decorators size", response, containsString("Decorators.size = [2]"));
expected = String.format("decorator[] = %s", DummyUtilDecorator.class.getName());
assertThat("Specific Legacy Decorator", response, containsString(expected));

View File

@ -27,6 +27,9 @@ import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/**
*/
@ -34,9 +37,12 @@ import javax.servlet.ServletResponse;
@Deprecated
public class GzipFilter implements Filter
{
private static final Logger LOG = Log.getLogger(GzipFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
LOG.warn("GzipFilter is deprecated. Use GzipHandler");
}
@Override

View File

@ -0,0 +1,86 @@
//
// ========================================================================
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.util;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class DeprecationWarning implements Decorator
{
private static final Logger LOG = Log.getLogger(DeprecationWarning.class);
@Override
public <T> T decorate(T o)
{
if (o == null)
{
return null;
}
Class<?> clazz = o.getClass();
try
{
Deprecated depr = clazz.getAnnotation(Deprecated.class);
if (depr != null)
{
LOG.warn("Using @Deprecated Class {}",clazz.getName());
}
}
catch (Throwable t)
{
LOG.ignore(t);
}
verifyIndirectTypes(clazz.getSuperclass(),clazz,"Class");
for (Class<?> ifaceClazz : clazz.getInterfaces())
{
verifyIndirectTypes(ifaceClazz,clazz,"Interface");
}
return o;
}
private void verifyIndirectTypes(Class<?> superClazz, Class<?> clazz, String typeName)
{
try
{
// Report on super class deprecation too
while (superClazz != null && superClazz != Object.class)
{
Deprecated supDepr = superClazz.getAnnotation(Deprecated.class);
if (supDepr != null)
{
LOG.warn("Using indirect @Deprecated {} {} - (seen from {})",typeName,superClazz.getName(),clazz);
}
superClazz = superClazz.getSuperclass();
}
}
catch (Throwable t)
{
LOG.ignore(t);
}
}
@Override
public void destroy(Object o)
{
}
}

View File

@ -109,6 +109,7 @@ public class DecoratorsLegacyTest
}
}
@SuppressWarnings("deprecation")
private static class DummyLegacyDecorator implements org.eclipse.jetty.servlet.ServletContextHandler.Decorator
{
@Override
@ -136,6 +137,7 @@ public class DecoratorsLegacyTest
@Override
protected void configureServletContextHandler(ServletContextHandler context)
{
context.getObjectFactory().clear();
// Add decorator in the legacy way
context.addDecorator(new DummyLegacyDecorator());
}

View File

@ -136,6 +136,7 @@ public class DecoratorsTest
protected void configureServletContextHandler(ServletContextHandler context)
{
// Add decorator in the new util way
context.getObjectFactory().clear();
context.getObjectFactory().addDecorator(new DummyUtilDecorator());
}
};