Merge branch 'master' into gcloud-session-manager
This commit is contained in:
commit
71c2d79daa
|
@ -137,7 +137,18 @@ public class HTTP2Client extends ContainerLifeCycle
|
||||||
setByteBufferPool(new MappedByteBufferPool());
|
setByteBufferPool(new MappedByteBufferPool());
|
||||||
|
|
||||||
if (connectionFactory == null)
|
if (connectionFactory == null)
|
||||||
setClientConnectionFactory(new HTTP2ClientConnectionFactory());
|
{
|
||||||
|
HTTP2ClientConnectionFactory h2 = new HTTP2ClientConnectionFactory();
|
||||||
|
ALPNClientConnectionFactory alpn = new ALPNClientConnectionFactory(getExecutor(), h2, getProtocols());
|
||||||
|
setClientConnectionFactory((endPoint, context) ->
|
||||||
|
{
|
||||||
|
ClientConnectionFactory factory = h2;
|
||||||
|
SslContextFactory sslContextFactory = (SslContextFactory)context.get(SslClientConnectionFactory.SSL_CONTEXT_FACTORY_CONTEXT_KEY);
|
||||||
|
if (sslContextFactory != null)
|
||||||
|
factory = new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), alpn);
|
||||||
|
return factory.newConnection(endPoint, context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (sessions == null)
|
if (sessions == null)
|
||||||
{
|
{
|
||||||
|
@ -356,17 +367,7 @@ public class HTTP2Client extends ContainerLifeCycle
|
||||||
context.put(HTTP2ClientConnectionFactory.BYTE_BUFFER_POOL_CONTEXT_KEY, getByteBufferPool());
|
context.put(HTTP2ClientConnectionFactory.BYTE_BUFFER_POOL_CONTEXT_KEY, getByteBufferPool());
|
||||||
context.put(HTTP2ClientConnectionFactory.EXECUTOR_CONTEXT_KEY, getExecutor());
|
context.put(HTTP2ClientConnectionFactory.EXECUTOR_CONTEXT_KEY, getExecutor());
|
||||||
context.put(HTTP2ClientConnectionFactory.SCHEDULER_CONTEXT_KEY, getScheduler());
|
context.put(HTTP2ClientConnectionFactory.SCHEDULER_CONTEXT_KEY, getScheduler());
|
||||||
|
return getClientConnectionFactory().newConnection(endpoint, context);
|
||||||
ClientConnectionFactory factory = getClientConnectionFactory();
|
|
||||||
|
|
||||||
SslContextFactory sslContextFactory = (SslContextFactory)context.get(SslClientConnectionFactory.SSL_CONTEXT_FACTORY_CONTEXT_KEY);
|
|
||||||
if (sslContextFactory != null)
|
|
||||||
{
|
|
||||||
ALPNClientConnectionFactory alpn = new ALPNClientConnectionFactory(getExecutor(), factory, getProtocols());
|
|
||||||
factory = new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), alpn);
|
|
||||||
}
|
|
||||||
|
|
||||||
return factory.newConnection(endpoint, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,6 +15,38 @@
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy</id>
|
||||||
|
<phase>generate-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<artifactItems>
|
||||||
|
<artifactItem>
|
||||||
|
<groupId>org.mortbay.jetty.alpn</groupId>
|
||||||
|
<artifactId>alpn-boot</artifactId>
|
||||||
|
<version>${alpn.version}</version>
|
||||||
|
<type>jar</type>
|
||||||
|
<overWrite>false</overWrite>
|
||||||
|
<outputDirectory>${project.build.directory}/alpn</outputDirectory>
|
||||||
|
</artifactItem>
|
||||||
|
</artifactItems>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<argLine>-Xbootclasspath/p:${project.build.directory}/alpn/alpn-boot-${alpn.version}.jar</argLine>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
|
@ -22,19 +22,24 @@ import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.alpn.client.ALPNClientConnectionFactory;
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.client.HttpClientTransport;
|
import org.eclipse.jetty.client.HttpClientTransport;
|
||||||
import org.eclipse.jetty.client.HttpDestination;
|
import org.eclipse.jetty.client.HttpDestination;
|
||||||
import org.eclipse.jetty.client.Origin;
|
import org.eclipse.jetty.client.Origin;
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
|
import org.eclipse.jetty.http.HttpScheme;
|
||||||
import org.eclipse.jetty.http2.api.Session;
|
import org.eclipse.jetty.http2.api.Session;
|
||||||
import org.eclipse.jetty.http2.client.HTTP2Client;
|
import org.eclipse.jetty.http2.client.HTTP2Client;
|
||||||
|
import org.eclipse.jetty.http2.client.HTTP2ClientConnectionFactory;
|
||||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||||
import org.eclipse.jetty.util.Promise;
|
import org.eclipse.jetty.util.Promise;
|
||||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
|
||||||
@ManagedObject("The HTTP/2 client transport")
|
@ManagedObject("The HTTP/2 client transport")
|
||||||
public class HttpClientTransportOverHTTP2 extends ContainerLifeCycle implements HttpClientTransport
|
public class HttpClientTransportOverHTTP2 extends ContainerLifeCycle implements HttpClientTransport
|
||||||
|
@ -68,7 +73,7 @@ public class HttpClientTransportOverHTTP2 extends ContainerLifeCycle implements
|
||||||
addBean(client);
|
addBean(client);
|
||||||
super.doStart();
|
super.doStart();
|
||||||
|
|
||||||
this.connectionFactory = client.getClientConnectionFactory();
|
this.connectionFactory = new HTTP2ClientConnectionFactory();
|
||||||
client.setClientConnectionFactory((endPoint, context) ->
|
client.setClientConnectionFactory((endPoint, context) ->
|
||||||
{
|
{
|
||||||
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
|
@ -128,13 +133,21 @@ public class HttpClientTransportOverHTTP2 extends ContainerLifeCycle implements
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
client.connect(httpClient.getSslContextFactory(), address, listener, promise, context);
|
SslContextFactory sslContextFactory = null;
|
||||||
|
if (HttpScheme.HTTPS.is(destination.getScheme()))
|
||||||
|
sslContextFactory = httpClient.getSslContextFactory();
|
||||||
|
|
||||||
|
client.connect(sslContextFactory, address, listener, promise, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||||
{
|
{
|
||||||
return connectionFactory.newConnection(endPoint, context);
|
ClientConnectionFactory factory = connectionFactory;
|
||||||
|
SslContextFactory sslContextFactory = (SslContextFactory)context.get(SslClientConnectionFactory.SSL_CONTEXT_FACTORY_CONTEXT_KEY);
|
||||||
|
if (sslContextFactory != null)
|
||||||
|
factory = new ALPNClientConnectionFactory(client.getExecutor(), factory, client.getProtocols());
|
||||||
|
return factory.newConnection(endPoint, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpConnectionOverHTTP2 newHttpConnection(HttpDestination destination, Session session)
|
protected HttpConnectionOverHTTP2 newHttpConnection(HttpDestination destination, Session session)
|
||||||
|
|
|
@ -21,9 +21,13 @@ package org.eclipse.jetty.http2.client.http;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.eclipse.jetty.http2.client.HTTP2Client;
|
import org.eclipse.jetty.http2.client.HTTP2Client;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class HttpClientTransportOverHTTP2Test
|
public class HttpClientTransportOverHTTP2Test
|
||||||
|
@ -51,4 +55,24 @@ public class HttpClientTransportOverHTTP2Test
|
||||||
|
|
||||||
Assert.assertTrue(http2Client.isStopped());
|
Assert.assertTrue(http2Client.isStopped());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
@Test
|
||||||
|
public void testExternalServer() throws Exception
|
||||||
|
{
|
||||||
|
HTTP2Client http2Client = new HTTP2Client();
|
||||||
|
SslContextFactory sslContextFactory = new SslContextFactory();
|
||||||
|
HttpClient httpClient = new HttpClient(new HttpClientTransportOverHTTP2(http2Client), sslContextFactory);
|
||||||
|
Executor executor = new QueuedThreadPool();
|
||||||
|
httpClient.setExecutor(executor);
|
||||||
|
|
||||||
|
httpClient.start();
|
||||||
|
|
||||||
|
// ContentResponse response = httpClient.GET("https://http2.akamai.com/");
|
||||||
|
ContentResponse response = httpClient.GET("https://webtide.com/");
|
||||||
|
|
||||||
|
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
|
||||||
|
httpClient.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||||
|
#org.eclipse.jetty.client.LEVEL=DEBUG
|
||||||
|
org.eclipse.jetty.http2.hpack.LEVEL=INFO
|
||||||
|
#org.eclipse.jetty.http2.LEVEL=DEBUG
|
||||||
|
#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
|
|
@ -84,29 +84,42 @@ public abstract class AbstractConnection implements Connection
|
||||||
|
|
||||||
protected void failedCallback(final Callback callback, final Throwable x)
|
protected void failedCallback(final Callback callback, final Throwable x)
|
||||||
{
|
{
|
||||||
// TODO always dispatch failure ?
|
if (callback.isNonBlocking())
|
||||||
try
|
|
||||||
{
|
{
|
||||||
getExecutor().execute(new Runnable()
|
try
|
||||||
{
|
{
|
||||||
@Override
|
callback.failed(x);
|
||||||
public void run()
|
}
|
||||||
{
|
catch (Exception e)
|
||||||
try
|
{
|
||||||
{
|
LOG.warn(e);
|
||||||
callback.failed(x);
|
}
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
LOG.warn(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch(RejectedExecutionException e)
|
else
|
||||||
{
|
{
|
||||||
LOG.debug(e);
|
try
|
||||||
callback.failed(x);
|
{
|
||||||
|
getExecutor().execute(new Runnable()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
callback.failed(x);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOG.warn(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch(RejectedExecutionException e)
|
||||||
|
{
|
||||||
|
LOG.debug(e);
|
||||||
|
callback.failed(x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -216,8 +216,8 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint
|
||||||
isOpen()?"Open":"CLOSED",
|
isOpen()?"Open":"CLOSED",
|
||||||
isInputShutdown()?"ISHUT":"in",
|
isInputShutdown()?"ISHUT":"in",
|
||||||
isOutputShutdown()?"OSHUT":"out",
|
isOutputShutdown()?"OSHUT":"out",
|
||||||
_fillInterest.isInterested()?"R":"-",
|
_fillInterest.toStateString(),
|
||||||
_writeFlusher.isInProgress()?"W":"-",
|
_writeFlusher.toStateString(),
|
||||||
getIdleFor(),
|
getIdleFor(),
|
||||||
getIdleTimeout(),
|
getIdleTimeout(),
|
||||||
getConnection()==null?null:getConnection().getClass().getSimpleName());
|
getConnection()==null?null:getConnection().getClass().getSimpleName());
|
||||||
|
|
|
@ -138,7 +138,13 @@ public abstract class FillInterest
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return String.format("FillInterest@%x{%b,%s}", hashCode(), _interested.get(), _interested.get());
|
return String.format("FillInterest@%x{%b,%s}", hashCode(), _interested.get()!=null, _interested.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String toStateString()
|
||||||
|
{
|
||||||
|
return _interested.get()==null?"-":"FI";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -522,4 +522,23 @@ abstract public class WriteFlusher
|
||||||
{
|
{
|
||||||
return String.format("WriteFlusher@%x{%s}", hashCode(), _state.get());
|
return String.format("WriteFlusher@%x{%s}", hashCode(), _state.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String toStateString()
|
||||||
|
{
|
||||||
|
switch(_state.get().getType())
|
||||||
|
{
|
||||||
|
case WRITING:
|
||||||
|
return "W";
|
||||||
|
case PENDING:
|
||||||
|
return "P";
|
||||||
|
case COMPLETING:
|
||||||
|
return "C";
|
||||||
|
case IDLE:
|
||||||
|
return "-";
|
||||||
|
case FAILED:
|
||||||
|
return "F";
|
||||||
|
default:
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2435,25 +2435,6 @@ public class Request implements HttpServletRequest
|
||||||
@Override
|
@Override
|
||||||
public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException
|
public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException
|
||||||
{
|
{
|
||||||
if (getContext() == null)
|
throw new ServletException("HttpServletRequest.upgrade() not supported in Jetty");
|
||||||
throw new ServletException ("Unable to instantiate "+handlerClass);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//Instantiate an instance and inject it
|
|
||||||
T h = getContext().createInstance(handlerClass);
|
|
||||||
|
|
||||||
//TODO handle the rest of the upgrade process
|
|
||||||
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
if (e instanceof ServletException)
|
|
||||||
throw (ServletException)e;
|
|
||||||
throw new ServletException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
|
||||||
private static final Logger LOG = Log.getLogger(ServletHolder.class);
|
private static final Logger LOG = Log.getLogger(ServletHolder.class);
|
||||||
private int _initOrder = -1;
|
private int _initOrder = -1;
|
||||||
private boolean _initOnStartup=false;
|
private boolean _initOnStartup=false;
|
||||||
|
private boolean _initialized = false;
|
||||||
private Map<String, String> _roleMap;
|
private Map<String, String> _roleMap;
|
||||||
private String _forcedPath;
|
private String _forcedPath;
|
||||||
private String _runAsRole;
|
private String _runAsRole;
|
||||||
|
@ -81,7 +82,6 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
|
||||||
private ServletRegistration.Dynamic _registration;
|
private ServletRegistration.Dynamic _registration;
|
||||||
private JspContainer _jspContainer;
|
private JspContainer _jspContainer;
|
||||||
|
|
||||||
|
|
||||||
private transient Servlet _servlet;
|
private transient Servlet _servlet;
|
||||||
private transient Config _config;
|
private transient Config _config;
|
||||||
private transient long _unavailable;
|
private transient long _unavailable;
|
||||||
|
@ -396,21 +396,24 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
|
||||||
public void initialize ()
|
public void initialize ()
|
||||||
throws Exception
|
throws Exception
|
||||||
{
|
{
|
||||||
super.initialize();
|
if(!_initialized){
|
||||||
if (_extInstance || _initOnStartup)
|
super.initialize();
|
||||||
{
|
if (_extInstance || _initOnStartup)
|
||||||
try
|
|
||||||
{
|
{
|
||||||
initServlet();
|
try
|
||||||
}
|
{
|
||||||
catch(Exception e)
|
initServlet();
|
||||||
{
|
}
|
||||||
if (_servletHandler.isStartWithUnavailable())
|
catch(Exception e)
|
||||||
LOG.ignore(e);
|
{
|
||||||
else
|
if (_servletHandler.isStartWithUnavailable())
|
||||||
throw e;
|
LOG.ignore(e);
|
||||||
|
else
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -443,6 +446,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
|
||||||
_servlet=null;
|
_servlet=null;
|
||||||
|
|
||||||
_config=null;
|
_config=null;
|
||||||
|
_initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
|
|
@ -21,6 +21,8 @@ package org.eclipse.jetty.servlet;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.ServletInputStream;
|
import javax.servlet.ServletInputStream;
|
||||||
|
@ -88,6 +90,7 @@ public class DispatcherForwardTest
|
||||||
// 2. assert query => a=1 one
|
// 2. assert query => a=1 one
|
||||||
// 1. assert query => a=1 one
|
// 1. assert query => a=1 one
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
final String query1 = "a=1%20one";
|
final String query1 = "a=1%20one";
|
||||||
servlet1 = new HttpServlet()
|
servlet1 = new HttpServlet()
|
||||||
{
|
{
|
||||||
|
@ -100,6 +103,7 @@ public class DispatcherForwardTest
|
||||||
|
|
||||||
checkThat(req.getQueryString(),Matchers.equalTo(query1));
|
checkThat(req.getQueryString(),Matchers.equalTo(query1));
|
||||||
checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
|
checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
|
||||||
|
latch.countDown();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
servlet2 = new HttpServlet()
|
servlet2 = new HttpServlet()
|
||||||
|
@ -120,6 +124,7 @@ public class DispatcherForwardTest
|
||||||
"Connection: close\r\n" +
|
"Connection: close\r\n" +
|
||||||
"\r\n";
|
"\r\n";
|
||||||
String response = connector.getResponses(request);
|
String response = connector.getResponses(request);
|
||||||
|
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||||
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
|
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +136,8 @@ public class DispatcherForwardTest
|
||||||
// 2. assert query => a=2
|
// 2. assert query => a=2
|
||||||
// 1. assert query => a=1
|
// 1. assert query => a=1
|
||||||
|
|
||||||
final String query1 = "a=1$20one&b=2%20two";
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
final String query1 = "a=1%20one&b=2%20two";
|
||||||
final String query2 = "a=3%20three";
|
final String query2 = "a=3%20three";
|
||||||
final String query3 = "a=3%20three&b=2%20two";
|
final String query3 = "a=3%20three&b=2%20two";
|
||||||
servlet1 = new HttpServlet()
|
servlet1 = new HttpServlet()
|
||||||
|
@ -143,9 +149,10 @@ public class DispatcherForwardTest
|
||||||
|
|
||||||
req.getRequestDispatcher("/two?" + query2).forward(req, resp);
|
req.getRequestDispatcher("/two?" + query2).forward(req, resp);
|
||||||
|
|
||||||
checkThat(req.getQueryString(),Matchers.equalTo(query1));
|
checkThat(req.getQueryString(), Matchers.equalTo(query1));
|
||||||
checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
|
checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
|
||||||
checkThat(req.getParameter("b"),Matchers.equalTo("2 two"));
|
checkThat(req.getParameter("b"),Matchers.equalTo("2 two"));
|
||||||
|
latch.countDown();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
servlet2 = new HttpServlet()
|
servlet2 = new HttpServlet()
|
||||||
|
@ -167,6 +174,7 @@ public class DispatcherForwardTest
|
||||||
"Connection: close\r\n" +
|
"Connection: close\r\n" +
|
||||||
"\r\n";
|
"\r\n";
|
||||||
String response = connector.getResponses(request);
|
String response = connector.getResponses(request);
|
||||||
|
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||||
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
|
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,6 +186,7 @@ public class DispatcherForwardTest
|
||||||
// 2. assert query => a=1&b=2
|
// 2. assert query => a=1&b=2
|
||||||
// 1. assert query => a=1
|
// 1. assert query => a=1
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
final String query1 = "a=1%20one";
|
final String query1 = "a=1%20one";
|
||||||
final String query2 = "b=2%20two";
|
final String query2 = "b=2%20two";
|
||||||
final String query3 = "b=2%20two&a=1%20one";
|
final String query3 = "b=2%20two&a=1%20one";
|
||||||
|
@ -192,6 +201,7 @@ public class DispatcherForwardTest
|
||||||
|
|
||||||
checkThat(req.getQueryString(),Matchers.equalTo(query1));
|
checkThat(req.getQueryString(),Matchers.equalTo(query1));
|
||||||
checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
|
checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
|
||||||
|
latch.countDown();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
servlet2 = new HttpServlet()
|
servlet2 = new HttpServlet()
|
||||||
|
@ -213,6 +223,7 @@ public class DispatcherForwardTest
|
||||||
"Connection: close\r\n" +
|
"Connection: close\r\n" +
|
||||||
"\r\n";
|
"\r\n";
|
||||||
String response = connector.getResponses(request);
|
String response = connector.getResponses(request);
|
||||||
|
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||||
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
|
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,6 +235,7 @@ public class DispatcherForwardTest
|
||||||
// 2. assert query => a=1 + params => a=1,2
|
// 2. assert query => a=1 + params => a=1,2
|
||||||
// 1. assert query => a=1 + params => a=1,2
|
// 1. assert query => a=1 + params => a=1,2
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
final String query1 = "a=1%20one";
|
final String query1 = "a=1%20one";
|
||||||
final String form = "a=2%20two";
|
final String form = "a=2%20two";
|
||||||
servlet1 = new HttpServlet()
|
servlet1 = new HttpServlet()
|
||||||
|
@ -240,6 +252,7 @@ public class DispatcherForwardTest
|
||||||
checkThat(values, Matchers.notNullValue());
|
checkThat(values, Matchers.notNullValue());
|
||||||
checkThat(2, Matchers.equalTo(values.length));
|
checkThat(2, Matchers.equalTo(values.length));
|
||||||
checkThat(values, Matchers.arrayContainingInAnyOrder("1 one", "2 two"));
|
checkThat(values, Matchers.arrayContainingInAnyOrder("1 one", "2 two"));
|
||||||
|
latch.countDown();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
servlet2 = new HttpServlet()
|
servlet2 = new HttpServlet()
|
||||||
|
@ -266,6 +279,7 @@ public class DispatcherForwardTest
|
||||||
"\r\n" +
|
"\r\n" +
|
||||||
form;
|
form;
|
||||||
String response = connector.getResponses(request);
|
String response = connector.getResponses(request);
|
||||||
|
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||||
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
|
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,6 +291,7 @@ public class DispatcherForwardTest
|
||||||
// 2. assert query => a=3 + params => a=3,2,1
|
// 2. assert query => a=3 + params => a=3,2,1
|
||||||
// 1. assert query => a=1 + params => a=1,2
|
// 1. assert query => a=1 + params => a=1,2
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
final String query1 = "a=1%20one";
|
final String query1 = "a=1%20one";
|
||||||
final String query2 = "a=3%20three";
|
final String query2 = "a=3%20three";
|
||||||
final String form = "a=2%20two";
|
final String form = "a=2%20two";
|
||||||
|
@ -294,6 +309,7 @@ public class DispatcherForwardTest
|
||||||
checkThat(values, Matchers.notNullValue());
|
checkThat(values, Matchers.notNullValue());
|
||||||
checkThat(2, Matchers.equalTo(values.length));
|
checkThat(2, Matchers.equalTo(values.length));
|
||||||
checkThat(values, Matchers.arrayContainingInAnyOrder("1 one", "2 two"));
|
checkThat(values, Matchers.arrayContainingInAnyOrder("1 one", "2 two"));
|
||||||
|
latch.countDown();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
servlet2 = new HttpServlet()
|
servlet2 = new HttpServlet()
|
||||||
|
@ -320,6 +336,7 @@ public class DispatcherForwardTest
|
||||||
"\r\n" +
|
"\r\n" +
|
||||||
form;
|
form;
|
||||||
String response = connector.getResponses(request);
|
String response = connector.getResponses(request);
|
||||||
|
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||||
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
|
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,6 +348,7 @@ public class DispatcherForwardTest
|
||||||
// 2. assert query => a=1&c=3 + params => a=1&b=2&c=3
|
// 2. assert query => a=1&c=3 + params => a=1&b=2&c=3
|
||||||
// 1. assert query => a=1 + params => a=1&b=2
|
// 1. assert query => a=1 + params => a=1&b=2
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
final String query1 = "a=1%20one";
|
final String query1 = "a=1%20one";
|
||||||
final String query2 = "c=3%20three";
|
final String query2 = "c=3%20three";
|
||||||
final String query3 = "c=3%20three&a=1%20one";
|
final String query3 = "c=3%20three&a=1%20one";
|
||||||
|
@ -348,6 +366,7 @@ public class DispatcherForwardTest
|
||||||
checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
|
checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
|
||||||
checkThat(req.getParameter("b"),Matchers.equalTo("2 two"));
|
checkThat(req.getParameter("b"),Matchers.equalTo("2 two"));
|
||||||
checkThat(req.getParameter("c"), Matchers.nullValue());
|
checkThat(req.getParameter("c"), Matchers.nullValue());
|
||||||
|
latch.countDown();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
servlet2 = new HttpServlet()
|
servlet2 = new HttpServlet()
|
||||||
|
@ -373,6 +392,7 @@ public class DispatcherForwardTest
|
||||||
"\r\n" +
|
"\r\n" +
|
||||||
form;
|
form;
|
||||||
String response = connector.getResponses(request);
|
String response = connector.getResponses(request);
|
||||||
|
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||||
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
|
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,6 +405,7 @@ public class DispatcherForwardTest
|
||||||
// 2. assert query => a=1&c=3 + params => a=1&b=2&c=3
|
// 2. assert query => a=1&c=3 + params => a=1&b=2&c=3
|
||||||
// 1. assert query => a=1 + params => a=1&b=2
|
// 1. assert query => a=1 + params => a=1&b=2
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
final String query1 = "a=1%20one";
|
final String query1 = "a=1%20one";
|
||||||
final String query2 = "c=3%20three";
|
final String query2 = "c=3%20three";
|
||||||
final String query3 = "c=3%20three&a=1%20one";
|
final String query3 = "c=3%20three&a=1%20one";
|
||||||
|
@ -404,6 +425,7 @@ public class DispatcherForwardTest
|
||||||
checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
|
checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
|
||||||
checkThat(req.getParameter("b"),Matchers.equalTo("2 two"));
|
checkThat(req.getParameter("b"),Matchers.equalTo("2 two"));
|
||||||
checkThat(req.getParameter("c"), Matchers.nullValue());
|
checkThat(req.getParameter("c"), Matchers.nullValue());
|
||||||
|
latch.countDown();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
servlet2 = new HttpServlet()
|
servlet2 = new HttpServlet()
|
||||||
|
@ -429,12 +451,14 @@ public class DispatcherForwardTest
|
||||||
"\r\n" +
|
"\r\n" +
|
||||||
form;
|
form;
|
||||||
String response = connector.getResponses(request);
|
String response = connector.getResponses(request);
|
||||||
|
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||||
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
|
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testContentCanBeReadViaInputStreamAfterForwardWithoutQuery() throws Exception
|
public void testContentCanBeReadViaInputStreamAfterForwardWithoutQuery() throws Exception
|
||||||
{
|
{
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
final String query1 = "a=1%20one";
|
final String query1 = "a=1%20one";
|
||||||
final String form = "c=3%20three";
|
final String form = "c=3%20three";
|
||||||
servlet1 = new HttpServlet()
|
servlet1 = new HttpServlet()
|
||||||
|
@ -448,6 +472,7 @@ public class DispatcherForwardTest
|
||||||
|
|
||||||
checkThat(req.getQueryString(),Matchers.equalTo(query1));
|
checkThat(req.getQueryString(),Matchers.equalTo(query1));
|
||||||
checkThat(req.getParameter("c"), Matchers.nullValue());
|
checkThat(req.getParameter("c"), Matchers.nullValue());
|
||||||
|
latch.countDown();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
servlet2 = new HttpServlet()
|
servlet2 = new HttpServlet()
|
||||||
|
@ -473,12 +498,14 @@ public class DispatcherForwardTest
|
||||||
"\r\n" +
|
"\r\n" +
|
||||||
form;
|
form;
|
||||||
String response = connector.getResponses(request);
|
String response = connector.getResponses(request);
|
||||||
|
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||||
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
|
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testContentCanBeReadViaInputStreamAfterForwardWithQuery() throws Exception
|
public void testContentCanBeReadViaInputStreamAfterForwardWithQuery() throws Exception
|
||||||
{
|
{
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
final String query1 = "a=1%20one";
|
final String query1 = "a=1%20one";
|
||||||
final String query2 = "b=2%20two";
|
final String query2 = "b=2%20two";
|
||||||
final String query3 = "b=2%20two&a=1%20one";
|
final String query3 = "b=2%20two&a=1%20one";
|
||||||
|
@ -494,6 +521,7 @@ public class DispatcherForwardTest
|
||||||
|
|
||||||
checkThat(req.getQueryString(),Matchers.equalTo(query1));
|
checkThat(req.getQueryString(),Matchers.equalTo(query1));
|
||||||
checkThat(req.getParameter("c"), Matchers.nullValue());
|
checkThat(req.getParameter("c"), Matchers.nullValue());
|
||||||
|
latch.countDown();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
servlet2 = new HttpServlet()
|
servlet2 = new HttpServlet()
|
||||||
|
@ -520,6 +548,7 @@ public class DispatcherForwardTest
|
||||||
"\r\n" +
|
"\r\n" +
|
||||||
form;
|
form;
|
||||||
String response = connector.getResponses(request);
|
String response = connector.getResponses(request);
|
||||||
|
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||||
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
|
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,213 +0,0 @@
|
||||||
//
|
|
||||||
// ========================================================================
|
|
||||||
// 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.servlets;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.nio.channels.SelectionKey;
|
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.nio.file.StandardOpenOption;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.io.ManagedSelector;
|
|
||||||
import org.eclipse.jetty.io.SelectChannelEndPoint;
|
|
||||||
import org.eclipse.jetty.server.Server;
|
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
|
||||||
import org.eclipse.jetty.servlet.DefaultServlet;
|
|
||||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
|
||||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
|
||||||
import org.eclipse.jetty.toolchain.test.TestTracker;
|
|
||||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class DefaultServletStarvationTest
|
|
||||||
{
|
|
||||||
@Rule
|
|
||||||
public TestTracker tracker = new TestTracker();
|
|
||||||
private Server _server;
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void dispose() throws Exception
|
|
||||||
{
|
|
||||||
if (_server != null)
|
|
||||||
_server.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDefaultServletStarvation() throws Exception
|
|
||||||
{
|
|
||||||
int maxThreads = 2;
|
|
||||||
QueuedThreadPool threadPool = new QueuedThreadPool(maxThreads, maxThreads);
|
|
||||||
threadPool.setDetailedDump(true);
|
|
||||||
_server = new Server(threadPool);
|
|
||||||
|
|
||||||
// Prepare a big file to download.
|
|
||||||
File directory = MavenTestingUtils.getTargetTestingDir();
|
|
||||||
Files.createDirectories(directory.toPath());
|
|
||||||
String resourceName = "resource.bin";
|
|
||||||
Path resourcePath = Paths.get(directory.getPath(), resourceName);
|
|
||||||
try (OutputStream output = Files.newOutputStream(resourcePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE))
|
|
||||||
{
|
|
||||||
byte[] chunk = new byte[1024];
|
|
||||||
Arrays.fill(chunk,(byte)'X');
|
|
||||||
chunk[chunk.length-2]='\r';
|
|
||||||
chunk[chunk.length-1]='\n';
|
|
||||||
for (int i = 0; i < 256 * 1024; ++i)
|
|
||||||
output.write(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
final CountDownLatch writePending = new CountDownLatch(1);
|
|
||||||
ServerConnector connector = new ServerConnector(_server, 0, 1)
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
|
|
||||||
{
|
|
||||||
return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout())
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
protected void onIncompleteFlush()
|
|
||||||
{
|
|
||||||
super.onIncompleteFlush();
|
|
||||||
writePending.countDown();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
_server.addConnector(connector);
|
|
||||||
|
|
||||||
ServletContextHandler context = new ServletContextHandler(_server, "/");
|
|
||||||
context.setResourceBase(directory.toURI().toString());
|
|
||||||
context.addServlet(DefaultServlet.class, "/*").setAsyncSupported(false);
|
|
||||||
_server.setHandler(context);
|
|
||||||
|
|
||||||
_server.start();
|
|
||||||
|
|
||||||
List<Socket> sockets = new ArrayList<>();
|
|
||||||
for (int i = 0; i < maxThreads; ++i)
|
|
||||||
{
|
|
||||||
Socket socket = new Socket("localhost", connector.getLocalPort());
|
|
||||||
sockets.add(socket);
|
|
||||||
OutputStream output = socket.getOutputStream();
|
|
||||||
String request = "" +
|
|
||||||
"GET /" + resourceName + " HTTP/1.1\r\n" +
|
|
||||||
"Host: localhost\r\n" +
|
|
||||||
// "Connection: close\r\n" +
|
|
||||||
"\r\n";
|
|
||||||
output.write(request.getBytes(StandardCharsets.UTF_8));
|
|
||||||
output.flush();
|
|
||||||
Thread.sleep(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Wait for a the servlet to block.
|
|
||||||
Assert.assertTrue(writePending.await(5, TimeUnit.SECONDS));
|
|
||||||
|
|
||||||
Thread.sleep(1000);
|
|
||||||
_server.dumpStdErr();
|
|
||||||
Thread.sleep(1000);
|
|
||||||
|
|
||||||
|
|
||||||
ScheduledFuture<?> dumper = Executors.newSingleThreadScheduledExecutor().schedule(new Runnable()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
_server.dumpStdErr();
|
|
||||||
}
|
|
||||||
}, 10, TimeUnit.SECONDS);
|
|
||||||
|
|
||||||
|
|
||||||
long expected = Files.size(resourcePath);
|
|
||||||
byte[] buffer = new byte[48 * 1024];
|
|
||||||
for (Socket socket : sockets)
|
|
||||||
{
|
|
||||||
String socketString = socket.toString();
|
|
||||||
long total = 0;
|
|
||||||
InputStream input = socket.getInputStream();
|
|
||||||
|
|
||||||
// look for CRLFCRLF
|
|
||||||
StringBuilder header = new StringBuilder();
|
|
||||||
int state=0;
|
|
||||||
while (state<4 && header.length()<2048)
|
|
||||||
{
|
|
||||||
int ch=input.read();
|
|
||||||
if (ch<0)
|
|
||||||
break;
|
|
||||||
header.append((char)ch);
|
|
||||||
switch(state)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
if (ch=='\r')
|
|
||||||
state=1;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
if (ch=='\n')
|
|
||||||
state=2;
|
|
||||||
else
|
|
||||||
state=0;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
if (ch=='\r')
|
|
||||||
state=3;
|
|
||||||
else
|
|
||||||
state=0;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
if (ch=='\n')
|
|
||||||
state=4;
|
|
||||||
else
|
|
||||||
state=0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (total<expected)
|
|
||||||
{
|
|
||||||
int read=input.read(buffer);
|
|
||||||
if (read<0)
|
|
||||||
break;
|
|
||||||
total+=read;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.assertEquals(expected,total);
|
|
||||||
}
|
|
||||||
|
|
||||||
dumper.cancel(false);
|
|
||||||
|
|
||||||
// We could read everything, good.
|
|
||||||
for (Socket socket : sockets)
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,419 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// 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.servlets;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.SelectionKey;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.BrokenBarrierException;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.CyclicBarrier;
|
||||||
|
import java.util.concurrent.Exchanger;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.ManagedSelector;
|
||||||
|
import org.eclipse.jetty.io.SelectChannelEndPoint;
|
||||||
|
import org.eclipse.jetty.server.HttpChannel;
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
|
import org.eclipse.jetty.servlet.DefaultServlet;
|
||||||
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
|
import org.eclipse.jetty.toolchain.test.TestTracker;
|
||||||
|
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.StdErrLog;
|
||||||
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class ThreadStarvationTest
|
||||||
|
{
|
||||||
|
@Rule
|
||||||
|
public TestTracker tracker = new TestTracker();
|
||||||
|
private Server _server;
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void dispose() throws Exception
|
||||||
|
{
|
||||||
|
if (_server != null)
|
||||||
|
_server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Slow
|
||||||
|
public void testDefaultServletSuccess() throws Exception
|
||||||
|
{
|
||||||
|
int maxThreads = 10;
|
||||||
|
QueuedThreadPool threadPool = new QueuedThreadPool(maxThreads, maxThreads);
|
||||||
|
threadPool.setDetailedDump(true);
|
||||||
|
_server = new Server(threadPool);
|
||||||
|
|
||||||
|
// Prepare a big file to download.
|
||||||
|
File directory = MavenTestingUtils.getTargetTestingDir();
|
||||||
|
Files.createDirectories(directory.toPath());
|
||||||
|
String resourceName = "resource.bin";
|
||||||
|
Path resourcePath = Paths.get(directory.getPath(), resourceName);
|
||||||
|
try (OutputStream output = Files.newOutputStream(resourcePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE))
|
||||||
|
{
|
||||||
|
byte[] chunk = new byte[1024];
|
||||||
|
Arrays.fill(chunk,(byte)'X');
|
||||||
|
chunk[chunk.length-2]='\r';
|
||||||
|
chunk[chunk.length-1]='\n';
|
||||||
|
for (int i = 0; i < 256 * 1024; ++i)
|
||||||
|
output.write(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
final CountDownLatch writePending = new CountDownLatch(1);
|
||||||
|
ServerConnector connector = new ServerConnector(_server, 0, 1)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
|
||||||
|
{
|
||||||
|
return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout())
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void onIncompleteFlush()
|
||||||
|
{
|
||||||
|
super.onIncompleteFlush();
|
||||||
|
writePending.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
connector.setIdleTimeout(Long.MAX_VALUE);
|
||||||
|
_server.addConnector(connector);
|
||||||
|
|
||||||
|
ServletContextHandler context = new ServletContextHandler(_server, "/");
|
||||||
|
context.setResourceBase(directory.toURI().toString());
|
||||||
|
context.addServlet(DefaultServlet.class, "/*").setAsyncSupported(false);
|
||||||
|
_server.setHandler(context);
|
||||||
|
|
||||||
|
_server.start();
|
||||||
|
|
||||||
|
List<Socket> sockets = new ArrayList<>();
|
||||||
|
for (int i = 0; i < maxThreads*2; ++i)
|
||||||
|
{
|
||||||
|
Socket socket = new Socket("localhost", connector.getLocalPort());
|
||||||
|
sockets.add(socket);
|
||||||
|
OutputStream output = socket.getOutputStream();
|
||||||
|
String request = "" +
|
||||||
|
"GET /" + resourceName + " HTTP/1.1\r\n" +
|
||||||
|
"Host: localhost\r\n" +
|
||||||
|
"\r\n";
|
||||||
|
output.write(request.getBytes(StandardCharsets.UTF_8));
|
||||||
|
output.flush();
|
||||||
|
Thread.sleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for a the servlet to block.
|
||||||
|
Assert.assertTrue(writePending.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
long expected = Files.size(resourcePath);
|
||||||
|
byte[] buffer = new byte[48 * 1024];
|
||||||
|
List<Exchanger<Long>> totals = new ArrayList<>();
|
||||||
|
for (Socket socket : sockets)
|
||||||
|
{
|
||||||
|
final Exchanger<Long> x = new Exchanger<>();
|
||||||
|
totals.add(x);
|
||||||
|
final InputStream input = socket.getInputStream();
|
||||||
|
|
||||||
|
new Thread()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
long total=0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// look for CRLFCRLF
|
||||||
|
StringBuilder header = new StringBuilder();
|
||||||
|
int state=0;
|
||||||
|
while (state<4 && header.length()<2048)
|
||||||
|
{
|
||||||
|
int ch=input.read();
|
||||||
|
if (ch<0)
|
||||||
|
break;
|
||||||
|
header.append((char)ch);
|
||||||
|
switch(state)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
if (ch=='\r')
|
||||||
|
state=1;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (ch=='\n')
|
||||||
|
state=2;
|
||||||
|
else
|
||||||
|
state=0;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (ch=='\r')
|
||||||
|
state=3;
|
||||||
|
else
|
||||||
|
state=0;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
if (ch=='\n')
|
||||||
|
state=4;
|
||||||
|
else
|
||||||
|
state=0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (total<expected)
|
||||||
|
{
|
||||||
|
int read=input.read(buffer);
|
||||||
|
if (read<0)
|
||||||
|
break;
|
||||||
|
total+=read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
x.exchange(total);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Exchanger<Long> x : totals)
|
||||||
|
{
|
||||||
|
Long total = x.exchange(-1L,10000,TimeUnit.SECONDS);
|
||||||
|
Assert.assertEquals(expected,total.longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
// We could read everything, good.
|
||||||
|
for (Socket socket : sockets)
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFailureStarvation() throws Exception
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(true);
|
||||||
|
|
||||||
|
int acceptors = 0;
|
||||||
|
int selectors = 1;
|
||||||
|
int maxThreads = 10;
|
||||||
|
final int barried=maxThreads-acceptors-selectors;
|
||||||
|
final CyclicBarrier barrier = new CyclicBarrier(barried);
|
||||||
|
|
||||||
|
|
||||||
|
QueuedThreadPool threadPool = new QueuedThreadPool(maxThreads, maxThreads);
|
||||||
|
threadPool.setDetailedDump(true);
|
||||||
|
_server = new Server(threadPool);
|
||||||
|
|
||||||
|
|
||||||
|
ServerConnector connector = new ServerConnector(_server, acceptors, selectors)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
|
||||||
|
{
|
||||||
|
return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout())
|
||||||
|
{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean flush(ByteBuffer... buffers) throws IOException
|
||||||
|
{
|
||||||
|
super.flush(buffers[0]);
|
||||||
|
throw new IOException("TEST FAILURE");
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
connector.setIdleTimeout(Long.MAX_VALUE);
|
||||||
|
_server.addConnector(connector);
|
||||||
|
|
||||||
|
final AtomicInteger count = new AtomicInteger(0);
|
||||||
|
_server.setHandler(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
int c=count.getAndIncrement();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (c<barried)
|
||||||
|
{
|
||||||
|
barrier.await(10,TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InterruptedException | BrokenBarrierException | TimeoutException e)
|
||||||
|
{
|
||||||
|
throw new ServletException(e);
|
||||||
|
}
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
response.setStatus(200);
|
||||||
|
response.setContentLength(13);
|
||||||
|
response.getWriter().print("Hello World!\n");
|
||||||
|
response.getWriter().flush();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_server.start();
|
||||||
|
|
||||||
|
List<Socket> sockets = new ArrayList<>();
|
||||||
|
for (int i = 0; i < maxThreads*2; ++i)
|
||||||
|
{
|
||||||
|
Socket socket = new Socket("localhost", connector.getLocalPort());
|
||||||
|
sockets.add(socket);
|
||||||
|
OutputStream output = socket.getOutputStream();
|
||||||
|
String request = "" +
|
||||||
|
"GET / HTTP/1.1\r\n" +
|
||||||
|
"Host: localhost\r\n" +
|
||||||
|
// "Connection: close\r\n" +
|
||||||
|
"\r\n";
|
||||||
|
output.write(request.getBytes(StandardCharsets.UTF_8));
|
||||||
|
output.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
byte[] buffer = new byte[48 * 1024];
|
||||||
|
List<Exchanger<Integer>> totals = new ArrayList<>();
|
||||||
|
for (Socket socket : sockets)
|
||||||
|
{
|
||||||
|
final Exchanger<Integer> x = new Exchanger<>();
|
||||||
|
totals.add(x);
|
||||||
|
final InputStream input = socket.getInputStream();
|
||||||
|
|
||||||
|
new Thread()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
int read=0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// look for CRLFCRLF
|
||||||
|
StringBuilder header = new StringBuilder();
|
||||||
|
int state=0;
|
||||||
|
while (state<4 && header.length()<2048)
|
||||||
|
{
|
||||||
|
int ch=input.read();
|
||||||
|
if (ch<0)
|
||||||
|
break;
|
||||||
|
header.append((char)ch);
|
||||||
|
switch(state)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
if (ch=='\r')
|
||||||
|
state=1;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (ch=='\n')
|
||||||
|
state=2;
|
||||||
|
else
|
||||||
|
state=0;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (ch=='\r')
|
||||||
|
state=3;
|
||||||
|
else
|
||||||
|
state=0;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
if (ch=='\n')
|
||||||
|
state=4;
|
||||||
|
else
|
||||||
|
state=0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
read=input.read(buffer);
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
// e.printStackTrace();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
x.exchange(read);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Exchanger<Integer> x : totals)
|
||||||
|
{
|
||||||
|
Integer read = x.exchange(-1,10,TimeUnit.SECONDS);
|
||||||
|
Assert.assertEquals(-1,read.intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
// We could read everything, good.
|
||||||
|
for (Socket socket : sockets)
|
||||||
|
socket.close();
|
||||||
|
|
||||||
|
_server.stop();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,36 @@
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy</id>
|
||||||
|
<phase>generate-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<artifactItems>
|
||||||
|
<artifactItem>
|
||||||
|
<groupId>org.mortbay.jetty.alpn</groupId>
|
||||||
|
<artifactId>alpn-boot</artifactId>
|
||||||
|
<version>${alpn.version}</version>
|
||||||
|
<type>jar</type>
|
||||||
|
<overWrite>false</overWrite>
|
||||||
|
<outputDirectory>${project.build.directory}/alpn</outputDirectory>
|
||||||
|
</artifactItem>
|
||||||
|
</artifactItems>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<argLine>-Xbootclasspath/p:${project.build.directory}/alpn/alpn-boot-${alpn.version}.jar</argLine>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-deploy-plugin</artifactId>
|
<artifactId>maven-deploy-plugin</artifactId>
|
||||||
|
@ -47,6 +77,12 @@
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-alpn-server</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty.http2</groupId>
|
<groupId>org.eclipse.jetty.http2</groupId>
|
||||||
<artifactId>http2-http-client-transport</artifactId>
|
<artifactId>http2-http-client-transport</artifactId>
|
||||||
|
|
|
@ -18,22 +18,28 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http.client;
|
package org.eclipse.jetty.http.client;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.client.HttpClientTransport;
|
import org.eclipse.jetty.client.HttpClientTransport;
|
||||||
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
||||||
|
import org.eclipse.jetty.http2.HTTP2Cipher;
|
||||||
import org.eclipse.jetty.http2.client.HTTP2Client;
|
import org.eclipse.jetty.http2.client.HTTP2Client;
|
||||||
import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2;
|
import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2;
|
||||||
|
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
|
||||||
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
|
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
|
||||||
import org.eclipse.jetty.server.ConnectionFactory;
|
import org.eclipse.jetty.server.ConnectionFactory;
|
||||||
import org.eclipse.jetty.server.Handler;
|
import org.eclipse.jetty.server.Handler;
|
||||||
import org.eclipse.jetty.server.HttpConfiguration;
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||||
import org.eclipse.jetty.toolchain.test.TestTracker;
|
import org.eclipse.jetty.toolchain.test.TestTracker;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
|
@ -44,15 +50,16 @@ import org.junit.runners.Parameterized;
|
||||||
public abstract class AbstractTest
|
public abstract class AbstractTest
|
||||||
{
|
{
|
||||||
@Parameterized.Parameters(name = "transport: {0}")
|
@Parameterized.Parameters(name = "transport: {0}")
|
||||||
public static List<Object[]> parameters() throws Exception
|
public static Object[] parameters() throws Exception
|
||||||
{
|
{
|
||||||
return Arrays.asList(new Object[]{Transport.HTTP}, new Object[]{Transport.HTTP2});
|
return new Object[]{Transport.HTTP, Transport.HTTPS, Transport.H2C, Transport.H2};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public final TestTracker tracker = new TestTracker();
|
public final TestTracker tracker = new TestTracker();
|
||||||
|
|
||||||
protected final Transport transport;
|
protected final Transport transport;
|
||||||
|
protected SslContextFactory sslContextFactory;
|
||||||
protected Server server;
|
protected Server server;
|
||||||
protected ServerConnector connector;
|
protected ServerConnector connector;
|
||||||
protected HttpClient client;
|
protected HttpClient client;
|
||||||
|
@ -64,11 +71,18 @@ public abstract class AbstractTest
|
||||||
|
|
||||||
public void start(Handler handler) throws Exception
|
public void start(Handler handler) throws Exception
|
||||||
{
|
{
|
||||||
|
sslContextFactory = new SslContextFactory();
|
||||||
|
sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
|
||||||
|
sslContextFactory.setKeyStorePassword("storepwd");
|
||||||
|
sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks");
|
||||||
|
sslContextFactory.setTrustStorePassword("storepwd");
|
||||||
|
sslContextFactory.setUseCipherSuitesOrder(true);
|
||||||
|
sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR);
|
||||||
startServer(handler);
|
startServer(handler);
|
||||||
startClient();
|
startClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void startServer(Handler handler) throws Exception
|
private void startServer(Handler handler) throws Exception
|
||||||
{
|
{
|
||||||
QueuedThreadPool serverThreads = new QueuedThreadPool();
|
QueuedThreadPool serverThreads = new QueuedThreadPool();
|
||||||
serverThreads.setName("server");
|
serverThreads.setName("server");
|
||||||
|
@ -79,26 +93,58 @@ public abstract class AbstractTest
|
||||||
server.start();
|
server.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void startClient() throws Exception
|
private void startClient() throws Exception
|
||||||
{
|
{
|
||||||
QueuedThreadPool clientThreads = new QueuedThreadPool();
|
QueuedThreadPool clientThreads = new QueuedThreadPool();
|
||||||
clientThreads.setName("client");
|
clientThreads.setName("client");
|
||||||
client = new HttpClient(provideClientTransport(transport), null);
|
client = new HttpClient(provideClientTransport(transport), sslContextFactory);
|
||||||
client.setExecutor(clientThreads);
|
client.setExecutor(clientThreads);
|
||||||
client.start();
|
client.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConnectionFactory provideServerConnectionFactory(Transport transport)
|
private ConnectionFactory[] provideServerConnectionFactory(Transport transport)
|
||||||
{
|
{
|
||||||
|
List<ConnectionFactory> result = new ArrayList<>();
|
||||||
switch (transport)
|
switch (transport)
|
||||||
{
|
{
|
||||||
case HTTP:
|
case HTTP:
|
||||||
return new HttpConnectionFactory(new HttpConfiguration());
|
{
|
||||||
case HTTP2:
|
result.add(new HttpConnectionFactory(new HttpConfiguration()));
|
||||||
return new HTTP2ServerConnectionFactory(new HttpConfiguration());
|
break;
|
||||||
|
}
|
||||||
|
case HTTPS:
|
||||||
|
{
|
||||||
|
HttpConfiguration configuration = new HttpConfiguration();
|
||||||
|
configuration.addCustomizer(new SecureRequestCustomizer());
|
||||||
|
HttpConnectionFactory http = new HttpConnectionFactory(configuration);
|
||||||
|
SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, http.getProtocol());
|
||||||
|
result.add(ssl);
|
||||||
|
result.add(http);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case H2C:
|
||||||
|
{
|
||||||
|
result.add(new HTTP2CServerConnectionFactory(new HttpConfiguration()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case H2:
|
||||||
|
{
|
||||||
|
HttpConfiguration configuration = new HttpConfiguration();
|
||||||
|
configuration.addCustomizer(new SecureRequestCustomizer());
|
||||||
|
HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(configuration);
|
||||||
|
ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory("h2");
|
||||||
|
SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, alpn.getProtocol());
|
||||||
|
result.add(ssl);
|
||||||
|
result.add(alpn);
|
||||||
|
result.add(h2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
|
{
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return result.toArray(new ConnectionFactory[result.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpClientTransport provideClientTransport(Transport transport)
|
private HttpClientTransport provideClientTransport(Transport transport)
|
||||||
|
@ -106,10 +152,12 @@ public abstract class AbstractTest
|
||||||
switch (transport)
|
switch (transport)
|
||||||
{
|
{
|
||||||
case HTTP:
|
case HTTP:
|
||||||
|
case HTTPS:
|
||||||
{
|
{
|
||||||
return new HttpClientTransportOverHTTP(1);
|
return new HttpClientTransportOverHTTP(1);
|
||||||
}
|
}
|
||||||
case HTTP2:
|
case H2C:
|
||||||
|
case H2:
|
||||||
{
|
{
|
||||||
HTTP2Client http2Client = new HTTP2Client();
|
HTTP2Client http2Client = new HTTP2Client();
|
||||||
http2Client.setSelectors(1);
|
http2Client.setSelectors(1);
|
||||||
|
@ -122,6 +170,21 @@ public abstract class AbstractTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String newURI()
|
||||||
|
{
|
||||||
|
switch (transport)
|
||||||
|
{
|
||||||
|
case HTTP:
|
||||||
|
case H2C:
|
||||||
|
return "http://localhost:" + connector.getLocalPort();
|
||||||
|
case HTTPS:
|
||||||
|
case H2:
|
||||||
|
return "https://localhost:" + connector.getLocalPort();
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void stop() throws Exception
|
public void stop() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -133,6 +196,6 @@ public abstract class AbstractTest
|
||||||
|
|
||||||
protected enum Transport
|
protected enum Transport
|
||||||
{
|
{
|
||||||
HTTP, HTTP2
|
HTTP, HTTPS, H2C, H2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ public class AsyncRequestContentTest extends AbstractTest
|
||||||
|
|
||||||
DeferredContentProvider contentProvider = new DeferredContentProvider();
|
DeferredContentProvider contentProvider = new DeferredContentProvider();
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
client.POST("http://localhost:" + connector.getLocalPort())
|
client.POST(newURI())
|
||||||
.content(contentProvider)
|
.content(contentProvider)
|
||||||
.send(result ->
|
.send(result ->
|
||||||
{
|
{
|
||||||
|
@ -73,7 +73,7 @@ public class AsyncRequestContentTest extends AbstractTest
|
||||||
|
|
||||||
DeferredContentProvider contentProvider = new DeferredContentProvider();
|
DeferredContentProvider contentProvider = new DeferredContentProvider();
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
client.POST("http://localhost:" + connector.getLocalPort())
|
client.POST(newURI())
|
||||||
.content(contentProvider)
|
.content(contentProvider)
|
||||||
.send(result ->
|
.send(result ->
|
||||||
{
|
{
|
||||||
|
@ -95,7 +95,7 @@ public class AsyncRequestContentTest extends AbstractTest
|
||||||
InputStreamContentProvider contentProvider =
|
InputStreamContentProvider contentProvider =
|
||||||
new InputStreamContentProvider(new ByteArrayInputStream(new byte[0]));
|
new InputStreamContentProvider(new ByteArrayInputStream(new byte[0]));
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
client.POST("http://localhost:" + connector.getLocalPort())
|
client.POST(newURI())
|
||||||
.content(contentProvider)
|
.content(contentProvider)
|
||||||
.send(result ->
|
.send(result ->
|
||||||
{
|
{
|
||||||
|
@ -116,7 +116,7 @@ public class AsyncRequestContentTest extends AbstractTest
|
||||||
InputStreamContentProvider contentProvider =
|
InputStreamContentProvider contentProvider =
|
||||||
new InputStreamContentProvider(new ByteArrayInputStream(new byte[1]));
|
new InputStreamContentProvider(new ByteArrayInputStream(new byte[1]));
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
client.POST("http://localhost:" + connector.getLocalPort())
|
client.POST(newURI())
|
||||||
.content(contentProvider)
|
.content(contentProvider)
|
||||||
.send(result ->
|
.send(result ->
|
||||||
{
|
{
|
||||||
|
@ -136,7 +136,7 @@ public class AsyncRequestContentTest extends AbstractTest
|
||||||
|
|
||||||
OutputStreamContentProvider contentProvider = new OutputStreamContentProvider();
|
OutputStreamContentProvider contentProvider = new OutputStreamContentProvider();
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
client.POST("http://localhost:" + connector.getLocalPort())
|
client.POST(newURI())
|
||||||
.content(contentProvider)
|
.content(contentProvider)
|
||||||
.send(result ->
|
.send(result ->
|
||||||
{
|
{
|
||||||
|
@ -156,7 +156,7 @@ public class AsyncRequestContentTest extends AbstractTest
|
||||||
|
|
||||||
OutputStreamContentProvider contentProvider = new OutputStreamContentProvider();
|
OutputStreamContentProvider contentProvider = new OutputStreamContentProvider();
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
client.POST("http://localhost:" + connector.getLocalPort())
|
client.POST(newURI())
|
||||||
.content(contentProvider)
|
.content(contentProvider)
|
||||||
.send(result ->
|
.send(result ->
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,8 +27,6 @@ import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.api.Response;
|
|
||||||
import org.eclipse.jetty.client.api.Result;
|
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
@ -61,14 +59,10 @@ public class HttpClientIdleTimeoutTest extends AbstractTest
|
||||||
client.start();
|
client.start();
|
||||||
|
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
client.newRequest("localhost", connector.getLocalPort()).send(new Response.CompleteListener()
|
client.newRequest(newURI()).send(result ->
|
||||||
{
|
{
|
||||||
@Override
|
if (result.isFailed())
|
||||||
public void onComplete(Result result)
|
latch.countDown();
|
||||||
{
|
|
||||||
if (result.isFailed())
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.assertTrue(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
|
Assert.assertTrue(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
|
||||||
|
@ -89,16 +83,12 @@ public class HttpClientIdleTimeoutTest extends AbstractTest
|
||||||
});
|
});
|
||||||
|
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
client.newRequest("localhost", connector.getLocalPort())
|
client.newRequest(newURI())
|
||||||
.idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
|
.idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
|
||||||
.send(new Response.CompleteListener()
|
.send(result ->
|
||||||
{
|
{
|
||||||
@Override
|
if (result.isFailed())
|
||||||
public void onComplete(Result result)
|
latch.countDown();
|
||||||
{
|
|
||||||
if (result.isFailed())
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.assertTrue(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
|
Assert.assertTrue(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
|
||||||
|
|
|
@ -61,7 +61,7 @@ public class HttpClientTest extends AbstractTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
ContentResponse response = client.newRequest(newURI())
|
||||||
.timeout(5, TimeUnit.SECONDS)
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
.send();
|
.send();
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ public class HttpClientTest extends AbstractTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
org.eclipse.jetty.client.api.Request request = client.newRequest("localhost", connector.getLocalPort());
|
org.eclipse.jetty.client.api.Request request = client.newRequest(newURI());
|
||||||
FutureResponseListener listener = new FutureResponseListener(request, length);
|
FutureResponseListener listener = new FutureResponseListener(request, length);
|
||||||
request.timeout(10, TimeUnit.SECONDS).send(listener);
|
request.timeout(10, TimeUnit.SECONDS).send(listener);
|
||||||
ContentResponse response = listener.get();
|
ContentResponse response = listener.get();
|
||||||
|
@ -139,7 +139,7 @@ public class HttpClientTest extends AbstractTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
org.eclipse.jetty.client.api.Request request = client.newRequest("localhost", connector.getLocalPort());
|
org.eclipse.jetty.client.api.Request request = client.newRequest(newURI());
|
||||||
FutureResponseListener listener = new FutureResponseListener(request, 2 * length);
|
FutureResponseListener listener = new FutureResponseListener(request, 2 * length);
|
||||||
request.timeout(10, TimeUnit.SECONDS).send(listener);
|
request.timeout(10, TimeUnit.SECONDS).send(listener);
|
||||||
ContentResponse response = listener.get();
|
ContentResponse response = listener.get();
|
||||||
|
@ -183,7 +183,7 @@ public class HttpClientTest extends AbstractTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
ContentResponse response = client.newRequest(newURI())
|
||||||
.method(HttpMethod.POST)
|
.method(HttpMethod.POST)
|
||||||
.content(new BytesContentProvider(bytes))
|
.content(new BytesContentProvider(bytes))
|
||||||
.timeout(15, TimeUnit.SECONDS)
|
.timeout(15, TimeUnit.SECONDS)
|
||||||
|
@ -228,7 +228,7 @@ public class HttpClientTest extends AbstractTest
|
||||||
int chunkSize = 16;
|
int chunkSize = 16;
|
||||||
byte[][] bytes = IntStream.range(0, chunks).mapToObj(x -> new byte[chunkSize]).toArray(byte[][]::new);
|
byte[][] bytes = IntStream.range(0, chunks).mapToObj(x -> new byte[chunkSize]).toArray(byte[][]::new);
|
||||||
BytesContentProvider contentProvider = new BytesContentProvider("application/octet-stream", bytes);
|
BytesContentProvider contentProvider = new BytesContentProvider("application/octet-stream", bytes);
|
||||||
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
ContentResponse response = client.newRequest(newURI())
|
||||||
.method(HttpMethod.POST)
|
.method(HttpMethod.POST)
|
||||||
.content(contentProvider)
|
.content(contentProvider)
|
||||||
.timeout(15, TimeUnit.SECONDS)
|
.timeout(15, TimeUnit.SECONDS)
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue