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

This commit is contained in:
Simone Bordet 2019-04-06 13:45:54 +02:00
commit 40c2e76786
54 changed files with 1496 additions and 619 deletions

2
Jenkinsfile vendored
View File

@ -97,7 +97,7 @@ def mavenBuild(jdk, cmdline, mvnName, junitPublishDisabled) {
mavenOpts: mavenOpts, mavenOpts: mavenOpts,
mavenLocalRepo: localRepo) { mavenLocalRepo: localRepo) {
// Some common Maven command line + provided command line // Some common Maven command line + provided command line
sh "mvn -V -B -T3 -e -fae -Dmaven.test.failure.ignore=true -Djetty.testtracker.log=true $cmdline -Dunix.socket.tmp=" + env.JENKINS_HOME sh "mvn -Pci -V -B -T3 -e -fae -Dmaven.test.failure.ignore=true -Djetty.testtracker.log=true $cmdline -Dunix.socket.tmp=" + env.JENKINS_HOME
} }
} }

View File

@ -25,7 +25,6 @@ import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode; import java.nio.channels.FileChannel.MapMode;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import javax.servlet.AsyncContext; import javax.servlet.AsyncContext;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -116,7 +115,8 @@ public class FastFileServer
} }
String listing = Resource.newResource(file).getListHTML( String listing = Resource.newResource(file).getListHTML(
request.getRequestURI(), request.getRequestURI(),
request.getPathInfo().lastIndexOf("/") > 0); request.getPathInfo().lastIndexOf("/") > 0,
request.getQueryString());
response.setContentType("text/html; charset=utf-8"); response.setContentType("text/html; charset=utf-8");
response.getWriter().println(listing); response.getWriter().println(listing);
return; return;

View File

@ -91,6 +91,7 @@ import org.eclipse.jetty.util.SocketAddressResolver;
import org.eclipse.jetty.util.log.StacklessLogging; import org.eclipse.jetty.util.log.StacklessLogging;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty; import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
@ -740,6 +741,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
@ParameterizedTest @ParameterizedTest
@ArgumentsSource(ScenarioProvider.class) @ArgumentsSource(ScenarioProvider.class)
@Tag("ipv6")
public void testSendToIPv6Address(Scenario scenario) throws Exception public void testSendToIPv6Address(Scenario scenario) throws Exception
{ {
start(scenario, new EmptyServerHandler()); start(scenario, new EmptyServerHandler());
@ -1609,6 +1611,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
@ParameterizedTest @ParameterizedTest
@ArgumentsSource(ScenarioProvider.class) @ArgumentsSource(ScenarioProvider.class)
@Tag("ipv6")
public void test_IPv6_Host(Scenario scenario) throws Exception public void test_IPv6_Host(Scenario scenario) throws Exception
{ {
start(scenario, new AbstractHandler() start(scenario, new AbstractHandler()

View File

@ -18,13 +18,6 @@
package org.eclipse.jetty.client; package org.eclipse.jetty.client;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@ -52,13 +45,22 @@ import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.Fields; import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.URIUtil;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.provider.ArgumentsSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class HttpClientURITest extends AbstractHttpClientServerTest public class HttpClientURITest extends AbstractHttpClientServerTest
{ {
@ParameterizedTest @ParameterizedTest
@ArgumentsSource(ScenarioProvider.class) @ArgumentsSource(ScenarioProvider.class)
@Tag("ipv6")
public void testIPv6Host(Scenario scenario) throws Exception public void testIPv6Host(Scenario scenario) throws Exception
{ {
start(scenario, new EmptyServerHandler()); start(scenario, new EmptyServerHandler());

View File

@ -18,12 +18,12 @@
package org.eclipse.jetty.client; package org.eclipse.jetty.client;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
public class ProxyConfigurationTest public class ProxyConfigurationTest
{ {
@Test @Test
@ -68,6 +68,7 @@ public class ProxyConfigurationTest
} }
@Test @Test
@Tag("ipv6")
public void testProxyMatchesWithIncludesAndExcludesIPv6() throws Exception public void testProxyMatchesWithIncludesAndExcludesIPv6() throws Exception
{ {
HttpProxy proxy = new HttpProxy("host", 0); HttpProxy proxy = new HttpProxy("host", 0);

View File

@ -22,6 +22,7 @@ import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.AppLifeCycle; import org.eclipse.jetty.deploy.AppLifeCycle;
import org.eclipse.jetty.deploy.graph.Node; import org.eclipse.jetty.deploy.graph.Node;
import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Callback;
public class StandardDeployer implements AppLifeCycle.Binding public class StandardDeployer implements AppLifeCycle.Binding
{ {
@ -37,9 +38,10 @@ public class StandardDeployer implements AppLifeCycle.Binding
{ {
ContextHandler handler = app.getContextHandler(); ContextHandler handler = app.getContextHandler();
if (handler == null) if (handler == null)
{
throw new NullPointerException("No Handler created for App: " + app); throw new NullPointerException("No Handler created for App: " + app);
}
app.getDeploymentManager().getContexts().addHandler(handler); Callback.Completable blocker = new Callback.Completable();
app.getDeploymentManager().getContexts().deployHandler(handler, blocker);
blocker.get();
} }
} }

View File

@ -21,17 +21,12 @@ package org.eclipse.jetty.deploy.bindings;
import org.eclipse.jetty.deploy.App; import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.AppLifeCycle; import org.eclipse.jetty.deploy.AppLifeCycle;
import org.eclipse.jetty.deploy.graph.Node; import org.eclipse.jetty.deploy.graph.Node;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class StandardUndeployer implements AppLifeCycle.Binding public class StandardUndeployer implements AppLifeCycle.Binding
{ {
private static final Logger LOG = Log.getLogger(StandardUndeployer.class);
@Override @Override
public String[] getBindingTargets() public String[] getBindingTargets()
{ {
@ -42,33 +37,11 @@ public class StandardUndeployer implements AppLifeCycle.Binding
@Override @Override
public void processBinding(Node node, App app) throws Exception public void processBinding(Node node, App app) throws Exception
{ {
ContextHandler handler = app.getContextHandler(); ContextHandlerCollection contexts = app.getDeploymentManager().getContexts();
ContextHandlerCollection chcoll = app.getDeploymentManager().getContexts(); ContextHandler context = app.getContextHandler();
Callback.Completable blocker = new Callback.Completable();
recursiveRemoveContext(chcoll,handler); contexts.undeployHandler(context, blocker);
} blocker.get();
context.destroy();
private void recursiveRemoveContext(HandlerCollection coll, ContextHandler context)
{
Handler children[] = coll.getHandlers();
int originalCount = children.length;
for (int i = 0, n = children.length; i < n; i++)
{
Handler child = children[i];
LOG.debug("Child handler {}",child);
if (child.equals(context))
{
LOG.debug("Removing handler {}",child);
coll.removeHandler(child);
child.destroy();
if (LOG.isDebugEnabled())
LOG.debug("After removal: {} (originally {})",coll.getHandlers().length,originalCount);
}
else if (child instanceof HandlerCollection)
{
recursiveRemoveContext((HandlerCollection)child,context);
}
}
} }
} }

View File

@ -50,6 +50,7 @@ import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.IO; import org.eclipse.jetty.toolchain.test.IO;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.log.StacklessLogging; import org.eclipse.jetty.util.log.StacklessLogging;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty; import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
@ -473,10 +474,10 @@ public class HttpClientTest extends AbstractHttpClientServerTest
ExecutionException x = assertThrows(ExecutionException.class, () -> ExecutionException x = assertThrows(ExecutionException.class, () ->
client.newRequest("localhost", connector.getLocalPort()) client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme) .scheme(scheme)
.idleTimeout(4 * idleTimeout, TimeUnit.MILLISECONDS) .idleTimeout(4 * idleTimeout, TimeUnit.MILLISECONDS)
.timeout(3 * idleTimeout, TimeUnit.MILLISECONDS) .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
.send()); .send());
assertThat(x.getCause(), instanceOf(EOFException.class)); assertThat(x.getCause(), instanceOf(EOFException.class));
connector.setIdleTimeout(5 * idleTimeout); connector.setIdleTimeout(5 * idleTimeout);
@ -493,6 +494,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
} }
@Test @Test
@Tag("ipv6")
public void testSendToIPv6Address() throws Exception public void testSendToIPv6Address() throws Exception
{ {
start(new EmptyServerHandler()); start(new EmptyServerHandler());
@ -595,9 +597,9 @@ public class HttpClientTest extends AbstractHttpClientServerTest
{ {
assertThrows(ExecutionException.class, () -> assertThrows(ExecutionException.class, () ->
client.newRequest("localhost", connector.getLocalPort()) client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme) .scheme(scheme)
.timeout(60, TimeUnit.SECONDS) .timeout(60, TimeUnit.SECONDS)
.send()); .send());
} }
} }

View File

@ -13,7 +13,7 @@ session-store
sessions sessions
[files] [files]
maven://com.hazelcast/hazelcast/3.8.2|lib/hazelcast/hazelcast-3.8.2.jar maven://com.hazelcast/hazelcast/3.9.3|lib/hazelcast/hazelcast-3.9.3.jar
[xml] [xml]
etc/sessions/hazelcast/default.xml etc/sessions/hazelcast/default.xml
@ -33,4 +33,4 @@ jetty.session.hazelcast.mapName=jetty-distributed-session-map
jetty.session.hazelcast.hazelcastInstanceName=JETTY_DISTRIBUTED_SESSION_INSTANCE jetty.session.hazelcast.hazelcastInstanceName=JETTY_DISTRIBUTED_SESSION_INSTANCE
#jetty.session.hazelcast.configurationLocation= #jetty.session.hazelcast.configurationLocation=
jetty.session.gracePeriod.seconds=3600 jetty.session.gracePeriod.seconds=3600
jetty.session.savePeriod.seconds=0 jetty.session.savePeriod.seconds=0

View File

@ -13,8 +13,8 @@ session-store
sessions sessions
[files] [files]
maven://com.hazelcast/hazelcast/3.8.2|lib/hazelcast/hazelcast-3.8.2.jar maven://com.hazelcast/hazelcast/3.9.3|lib/hazelcast/hazelcast-3.9.3.jar
maven://com.hazelcast/hazelcast-client/3.8.2|lib/hazelcast/hazelcast-client-3.8.2.jar maven://com.hazelcast/hazelcast-client/3.9.3|lib/hazelcast/hazelcast-client-3.9.3.jar
[xml] [xml]
etc/sessions/hazelcast/remote.xml etc/sessions/hazelcast/remote.xml
@ -35,4 +35,4 @@ jetty.session.hazelcast.hazelcastInstanceName=JETTY_DISTRIBUTED_SESSION_INSTANCE
jetty.session.hazelcast.onlyClient=true jetty.session.hazelcast.onlyClient=true
#jetty.session.hazelcast.configurationLocation= #jetty.session.hazelcast.configurationLocation=
jetty.session.gracePeriod.seconds=3600 jetty.session.gracePeriod.seconds=3600
jetty.session.savePeriod.seconds=0 jetty.session.savePeriod.seconds=0

View File

@ -28,6 +28,7 @@ import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.StacklessLogging; import org.eclipse.jetty.util.log.StacklessLogging;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.eclipse.jetty.http.HttpCompliance.Violation.CASE_INSENSITIVE_METHOD; import static org.eclipse.jetty.http.HttpCompliance.Violation.CASE_INSENSITIVE_METHOD;
@ -1975,6 +1976,7 @@ public class HttpParserTest
} }
@Test @Test
@Tag("ipv6")
public void testIPv6Host() throws Exception public void testIPv6Host() throws Exception
{ {
ByteBuffer buffer = BufferUtil.toBuffer( ByteBuffer buffer = BufferUtil.toBuffer(
@ -2056,6 +2058,7 @@ public class HttpParserTest
} }
@Test @Test
@Tag("ipv6")
public void testIPv6HostPort() throws Exception public void testIPv6HostPort() throws Exception
{ {
ByteBuffer buffer = BufferUtil.toBuffer( ByteBuffer buffer = BufferUtil.toBuffer(

View File

@ -295,8 +295,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
} }
catch (Throwable x) catch (Throwable x)
{ {
if (LOG.isDebugEnabled()) LOG.ignore(x);
LOG.debug(x);
return -1; return -1;
} }
} }
@ -309,8 +308,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
} }
catch (Throwable x) catch (Throwable x)
{ {
if (LOG.isDebugEnabled()) LOG.ignore(x);
LOG.debug(x);
return -1; return -1;
} }
} }

View File

@ -389,9 +389,9 @@ abstract public class WriteFlusher
boolean progress = true; boolean progress = true;
while (progress && buffers != null) while (progress && buffers != null)
{ {
long before = remaining(buffers); long before = BufferUtil.remaining(buffers);
boolean flushed = _endPoint.flush(buffers); boolean flushed = _endPoint.flush(buffers);
long after = remaining(buffers); long after = BufferUtil.remaining(buffers);
long written = before - after; long written = before - after;
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
@ -441,16 +441,6 @@ abstract public class WriteFlusher
return buffers == null ? EMPTY_BUFFERS : buffers; return buffers == null ? EMPTY_BUFFERS : buffers;
} }
private long remaining(ByteBuffer[] buffers)
{
if (buffers == null)
return 0;
long result = 0;
for (ByteBuffer buffer : buffers)
result += buffer.remaining();
return result;
}
/** /**
* Notify the flusher of a failure * Notify the flusher of a failure
* *

View File

@ -835,6 +835,12 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
LOG.debug("flush b[{}]={}", i++, BufferUtil.toDetailString(b)); LOG.debug("flush b[{}]={}", i++, BufferUtil.toDetailString(b));
} }
// finish of any previous flushes
if (BufferUtil.hasContent(_encryptedOutput) && !getEndPoint().flush(_encryptedOutput))
return false;
boolean isEmpty = BufferUtil.isEmpty(appOuts);
Boolean result = null; Boolean result = null;
try try
{ {
@ -866,7 +872,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
if (filled < 0) if (filled < 0)
throw new IOException("Broken pipe"); throw new IOException("Broken pipe");
} }
return result = false; return result = isEmpty;
default: default:
throw new IllegalStateException("Unexpected HandshakeStatus " + status); throw new IllegalStateException("Unexpected HandshakeStatus " + status);
@ -895,10 +901,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
_sslEngine.isOutboundDone()); _sslEngine.isOutboundDone());
// Was all the data consumed? // Was all the data consumed?
boolean allConsumed = true; isEmpty = BufferUtil.isEmpty(appOuts);
for (ByteBuffer b : appOuts)
if (BufferUtil.hasContent(b))
allConsumed = false;
// if we have net bytes, let's try to flush them // if we have net bytes, let's try to flush them
boolean flushed = true; boolean flushed = true;
@ -906,7 +909,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
flushed = getEndPoint().flush(_encryptedOutput); flushed = getEndPoint().flush(_encryptedOutput);
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("net flushed={}, ac={}", flushed, allConsumed); LOG.debug("net flushed={}, ac={}", flushed, isEmpty);
// Now deal with the results returned from the wrap // Now deal with the results returned from the wrap
Status wrap = wrapResult.getStatus(); Status wrap = wrapResult.getStatus();
@ -919,7 +922,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
if (!flushed) if (!flushed)
return result = false; return result = false;
getEndPoint().shutdownOutput(); getEndPoint().shutdownOutput();
if (allConsumed) if (isEmpty)
return result = true; return result = true;
throw new IOException("Broken pipe"); throw new IOException("Broken pipe");
} }
@ -936,15 +939,20 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
if (isRenegotiating() && !allowRenegotiate()) if (isRenegotiating() && !allowRenegotiate())
{ {
getEndPoint().shutdownOutput(); getEndPoint().shutdownOutput();
if (allConsumed && BufferUtil.isEmpty(_encryptedOutput)) if (isEmpty && BufferUtil.isEmpty(_encryptedOutput))
return result = true; return result = true;
throw new IOException("Broken pipe"); throw new IOException("Broken pipe");
} }
if (!flushed) if (!flushed)
return result = false; return result = false;
if (allConsumed)
return result = true; if (isEmpty)
{
if (wrapResult.getHandshakeStatus() != HandshakeStatus.NEED_WRAP ||
wrapResult.bytesProduced() == 0)
return result = true;
}
break; break;
default: default:
@ -1073,14 +1081,15 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
@Override @Override
public void doShutdownOutput() public void doShutdownOutput()
{ {
final EndPoint endp = getEndPoint();
try try
{ {
boolean close; boolean close;
boolean flush = false; boolean flush = false;
synchronized (_decryptedEndPoint) synchronized (_decryptedEndPoint)
{ {
boolean ishut = getEndPoint().isInputShutdown(); boolean ishut = endp.isInputShutdown();
boolean oshut = getEndPoint().isOutputShutdown(); boolean oshut = endp.isOutputShutdown();
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("shutdownOutput: {} oshut={}, ishut={} {}", SslConnection.this, oshut, ishut); LOG.debug("shutdownOutput: {} oshut={}, ishut={} {}", SslConnection.this, oshut, ishut);
@ -1097,16 +1106,28 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
} }
if (flush) if (flush)
flush(BufferUtil.EMPTY_BUFFER); // Send the TLS close message. {
if (!flush(BufferUtil.EMPTY_BUFFER) && !close)
{
Thread.yield();
// if we still can't flush, but we are not closing the endpoint,
// let's just flush the encrypted output in the background.
// and continue as if we are closed. The assumption here is that
// the encrypted buffer will contain the entire close handshake
// and that a call to flush(EMPTY_BUFFER) is not needed.
endp.write(Callback.from(() -> {}, t -> endp.close()), _encryptedOutput);
}
}
if (close) if (close)
getEndPoint().close(); endp.close();
else else
ensureFillInterested(); ensureFillInterested();
} }
catch (Throwable x) catch (Throwable x)
{ {
LOG.ignore(x); LOG.ignore(x);
getEndPoint().close(); endp.close();
} }
} }

View File

@ -23,6 +23,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.Socket; import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel; import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey; import java.nio.channels.SelectionKey;
@ -31,6 +32,7 @@ import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
@ -50,6 +52,8 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
@ -63,6 +67,8 @@ public class SslConnectionTest
private final SslContextFactory _sslCtxFactory = new SslContextFactory.Server(); private final SslContextFactory _sslCtxFactory = new SslContextFactory.Server();
protected volatile EndPoint _lastEndp; protected volatile EndPoint _lastEndp;
private volatile boolean _testFill=true; private volatile boolean _testFill=true;
private volatile boolean _onXWriteThenShutdown=false;
private volatile FutureCallback _writeCallback; private volatile FutureCallback _writeCallback;
protected ServerSocketChannel _connector; protected ServerSocketChannel _connector;
final AtomicInteger _dispatches = new AtomicInteger(); final AtomicInteger _dispatches = new AtomicInteger();
@ -104,6 +110,7 @@ public class SslConnectionTest
static final AtomicInteger __startBlocking = new AtomicInteger(); static final AtomicInteger __startBlocking = new AtomicInteger();
static final AtomicInteger __blockFor = new AtomicInteger(); static final AtomicInteger __blockFor = new AtomicInteger();
static final AtomicBoolean __onIncompleteFlush = new AtomicBoolean();
private static class TestEP extends SocketChannelEndPoint private static class TestEP extends SocketChannelEndPoint
{ {
public TestEP(SelectableChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler) public TestEP(SelectableChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler)
@ -114,13 +121,14 @@ public class SslConnectionTest
@Override @Override
protected void onIncompleteFlush() protected void onIncompleteFlush()
{ {
super.onIncompleteFlush(); __onIncompleteFlush.set(true);
} }
@Override @Override
public boolean flush(ByteBuffer... buffers) throws IOException public boolean flush(ByteBuffer... buffers) throws IOException
{ {
__onIncompleteFlush.set(false);
if (__startBlocking.get()==0 || __startBlocking.decrementAndGet()==0) if (__startBlocking.get()==0 || __startBlocking.decrementAndGet()==0)
{ {
if (__blockFor.get()>0 && __blockFor.getAndDecrement()>0) if (__blockFor.get()>0 && __blockFor.getAndDecrement()>0)
@ -224,20 +232,23 @@ public class SslConnectionTest
filled=endp.fill(_in); filled=endp.fill(_in);
} }
boolean shutdown = _onXWriteThenShutdown && BufferUtil.toString(_in).contains("X");
// Write everything // Write everything
int l=_in.remaining(); int l=_in.remaining();
if (l>0) if (l>0)
{ {
FutureCallback blockingWrite= new FutureCallback(); FutureCallback blockingWrite= new FutureCallback();
endp.write(blockingWrite,_in); endp.write(blockingWrite,_in);
blockingWrite.get(); blockingWrite.get();
if (shutdown)
endp.shutdownOutput();
} }
// are we done? // are we done?
if (endp.isInputShutdown()) if (endp.isInputShutdown() || shutdown)
{
endp.shutdownOutput(); endp.shutdownOutput();
}
} }
} }
catch(InterruptedException|EofException e) catch(InterruptedException|EofException e)
@ -423,7 +434,7 @@ public class SslConnectionTest
public void testBlockedWrite() throws Exception public void testBlockedWrite() throws Exception
{ {
startSSL(); startSSL();
try (Socket client = newClient()) try (SSLSocket client = newClient())
{ {
client.setSoTimeout(5000); client.setSoTimeout(5000);
try (SocketChannel server = _connector.accept()) try (SocketChannel server = _connector.accept())
@ -431,21 +442,78 @@ public class SslConnectionTest
server.configureBlocking(false); server.configureBlocking(false);
_manager.accept(server); _manager.accept(server);
__startBlocking.set(5);
__blockFor.set(3);
client.getOutputStream().write("Hello".getBytes(StandardCharsets.UTF_8)); client.getOutputStream().write("Hello".getBytes(StandardCharsets.UTF_8));
byte[] buffer = new byte[1024]; byte[] buffer = new byte[1024];
int len = client.getInputStream().read(buffer); int len = client.getInputStream().read(buffer);
assertEquals(5, len);
assertEquals("Hello", new String(buffer, 0, len, StandardCharsets.UTF_8)); assertEquals("Hello", new String(buffer, 0, len, StandardCharsets.UTF_8));
__startBlocking.set(0);
__blockFor.set(2);
_dispatches.set(0); _dispatches.set(0);
client.getOutputStream().write("World".getBytes(StandardCharsets.UTF_8)); client.getOutputStream().write("World".getBytes(StandardCharsets.UTF_8));
len = 5;
while (len > 0) try
len -= client.getInputStream().read(buffer); {
assertEquals(0, len); client.setSoTimeout(500);
client.getInputStream().read(buffer);
throw new IllegalStateException();
}
catch(SocketTimeoutException e)
{
}
assertTrue(__onIncompleteFlush.get());
((TestEP)_lastEndp).getWriteFlusher().completeWrite();
len = client.getInputStream().read(buffer);
assertEquals("World", new String(buffer, 0, len, StandardCharsets.UTF_8));
}
}
}
@Test
public void testBlockedClose() throws Exception
{
startSSL();
try (SSLSocket client = newClient())
{
client.setSoTimeout(5000);
try (SocketChannel server = _connector.accept())
{
server.configureBlocking(false);
_manager.accept(server);
//__startBlocking.set(5);
//__blockFor.set(3);
client.getOutputStream().write("Short".getBytes(StandardCharsets.UTF_8));
byte[] buffer = new byte[1024];
int len = client.getInputStream().read(buffer);
assertEquals("Short", new String(buffer, 0, len, StandardCharsets.UTF_8));
_onXWriteThenShutdown=true;
__startBlocking.set(2); // block on the close handshake flush
__blockFor.set(Integer.MAX_VALUE); // > retry loops in SslConnection + 1
client.getOutputStream().write("This is a much longer example with X".getBytes(StandardCharsets.UTF_8));
len = client.getInputStream().read(buffer);
assertEquals("This is a much longer example with X", new String(buffer, 0, len, StandardCharsets.UTF_8));
try
{
client.setSoTimeout(500);
client.getInputStream().read(buffer);
throw new IllegalStateException();
}
catch(SocketTimeoutException e)
{
}
__blockFor.set(0);
assertTrue(__onIncompleteFlush.get());
((TestEP)_lastEndp).getWriteFlusher().completeWrite();
len = client.getInputStream().read(buffer);
assertThat(len, is(len));
} }
} }
} }
@ -475,7 +543,6 @@ public class SslConnectionTest
String line = in.readLine(); String line = in.readLine();
if (line == null) if (line == null)
break; break;
// System.err.println(line);
count.countDown(); count.countDown();
} }
} }
@ -488,7 +555,6 @@ public class SslConnectionTest
for (int i = 0; i < LINES; i++) for (int i = 0; i < LINES; i++)
{ {
client.getOutputStream().write(("HelloWorld " + i + "\n").getBytes(StandardCharsets.UTF_8)); client.getOutputStream().write(("HelloWorld " + i + "\n").getBytes(StandardCharsets.UTF_8));
// System.err.println("wrote");
if (i % 1000 == 0) if (i % 1000 == 0)
{ {
client.getOutputStream().flush(); client.getOutputStream().flush();

View File

@ -24,7 +24,7 @@
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId> <artifactId>slf4j-simple</artifactId>
<version>1.7.9</version> <version>${slf4j.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -47,6 +47,7 @@ import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.Promise;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ -88,6 +89,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
} }
@Test @Test
@Tag("ipv6")
public void testCONNECTwithIPv6() throws Exception public void testCONNECTwithIPv6() throws Exception
{ {
String hostPort = "[::1]:" + serverConnector.getLocalPort(); String hostPort = "[::1]:" + serverConnector.getLocalPort();

View File

@ -1618,9 +1618,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
if (getServer() != null && (getServer().isStarting() || getServer().isStarted())) if (getServer() != null && (getServer().isStarting() || getServer().isStarted()))
{ {
Handler[] contextCollections = getServer().getChildHandlersByClass(ContextHandlerCollection.class); ContextHandlerCollection[] contextCollections =
(ContextHandlerCollection[])getServer().getChildHandlersByClass(ContextHandlerCollection.class);
for (int h = 0; contextCollections != null && h < contextCollections.length; h++) for (int h = 0; contextCollections != null && h < contextCollections.length; h++)
((ContextHandlerCollection)contextCollections[h]).mapContexts(); contextCollections[h].mapContexts();
} }
} }

View File

@ -24,8 +24,7 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -36,15 +35,16 @@ import org.eclipse.jetty.server.HttpChannelState;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.ArrayTernaryTrie; import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.ArrayUtil; import org.eclipse.jetty.util.ArrayUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Trie; import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation; import org.eclipse.jetty.util.annotation.ManagedOperation;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.SerializedExecutor;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** ContextHandlerCollection. /**
*
* This {@link org.eclipse.jetty.server.handler.HandlerCollection} is creates a * This {@link org.eclipse.jetty.server.handler.HandlerCollection} is creates a
* Map of contexts to it's contained handlers based * Map of contexts to it's contained handlers based
* on the context path and virtual hosts of any contained {@link org.eclipse.jetty.server.handler.ContextHandler}s. * on the context path and virtual hosts of any contained {@link org.eclipse.jetty.server.handler.ContextHandler}s.
@ -57,9 +57,9 @@ import org.eclipse.jetty.util.log.Logger;
public class ContextHandlerCollection extends HandlerCollection public class ContextHandlerCollection extends HandlerCollection
{ {
private static final Logger LOG = Log.getLogger(ContextHandlerCollection.class); private static final Logger LOG = Log.getLogger(ContextHandlerCollection.class);
private final SerializedExecutor _serializedExecutor = new SerializedExecutor();
private final ConcurrentMap<ContextHandler,Handler> _contextBranches = new ConcurrentHashMap<>(); @Deprecated
private volatile Trie<Map.Entry<String,Branch[]>> _pathBranches;
private Class<? extends ContextHandler> _contextClass = ContextHandler.class; private Class<? extends ContextHandler> _contextClass = ContextHandler.class;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -71,43 +71,57 @@ public class ContextHandlerCollection extends HandlerCollection
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public ContextHandlerCollection(ContextHandler... contexts) public ContextHandlerCollection(ContextHandler... contexts)
{ {
super(true,contexts); super(true);
setHandlers(contexts);
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* Remap the context paths. * Remap the contexts. Normally this is not required as context
* mapping is maintained as a side effect of {@link #setHandlers(Handler[])}
* However, if configuration changes in the deep handler structure (eg contextpath is changed), then
* this call will trigger a remapping.
* This method is mutually excluded from {@link #deployHandler(Handler, Callback)} and
* {@link #undeployHandler(Handler, Callback)}
*/ */
@ManagedOperation("update the mapping of context path to context") @ManagedOperation("Update the mapping of context path to context")
public void mapContexts() public void mapContexts()
{ {
_contextBranches.clear(); _serializedExecutor.execute(()->
Handler[] handlers = getHandlers();
if (handlers==null)
{ {
_pathBranches=new ArrayTernaryTrie<>(false,16); while(true)
return; {
} Handlers handlers = _handlers.get();
if (handlers==null)
break;
if (updateHandlers(handlers, newHandlers(handlers.getHandlers())))
break;
}
});
}
/* ------------------------------------------------------------ */
@Override
protected Handlers newHandlers(Handler[] handlers)
{
if (handlers==null || handlers.length==0)
return null;
// Create map of contextPath to handler Branch // Create map of contextPath to handler Branch
Map<String,Branch[]> map = new HashMap<>(); // A branch is a Handler that could contain 0 or more ContextHandlers
Map<String,Branch[]> path2Branches = new HashMap<>();
for (Handler handler:handlers) for (Handler handler:handlers)
{ {
Branch branch=new Branch(handler); Branch branch=new Branch(handler);
for (String contextPath : branch.getContextPaths()) for (String contextPath : branch.getContextPaths())
{ {
Branch[] branches=map.get(contextPath); Branch[] branches=path2Branches.get(contextPath);
map.put(contextPath, ArrayUtil.addToArray(branches, branch, Branch.class)); path2Branches.put(contextPath, ArrayUtil.addToArray(branches, branch, Branch.class));
} }
for (ContextHandler context : branch.getContextHandlers())
_contextBranches.putIfAbsent(context, branch.getHandler());
} }
// Sort the branches so those with virtual hosts are considered before those without // Sort the branches for each contextPath so those with virtual hosts are considered before those without
for (Map.Entry<String,Branch[]> entry: map.entrySet()) for (Map.Entry<String,Branch[]> entry: path2Branches.entrySet())
{ {
Branch[] branches=entry.getValue(); Branch[] branches=entry.getValue();
Branch[] sorted=new Branch[branches.length]; Branch[] sorted=new Branch[branches.length];
@ -123,69 +137,56 @@ public class ContextHandlerCollection extends HandlerCollection
// Loop until we have a big enough trie to hold all the context paths // Loop until we have a big enough trie to hold all the context paths
int capacity=512; int capacity=512;
Trie<Map.Entry<String,Branch[]>> trie; Mapping mapping;
loop: while(true) loop: while(true)
{ {
trie=new ArrayTernaryTrie<>(false,capacity); mapping = new Mapping(handlers, capacity);
for (Map.Entry<String,Branch[]> entry: map.entrySet()) for (Map.Entry<String,Branch[]> entry: path2Branches.entrySet())
{ {
if (!trie.put(entry.getKey().substring(1),entry)) if (!mapping._pathBranches.put(entry.getKey().substring(1),entry))
{ {
capacity+=512; capacity+=512;
continue loop; continue loop;
} }
} }
break loop; break;
} }
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
{ {
for (String ctx : trie.keySet()) for (String ctx : mapping._pathBranches.keySet())
LOG.debug("{}->{}",ctx,Arrays.asList(trie.get(ctx).getValue())); LOG.debug("{}->{}",ctx,Arrays.asList(mapping._pathBranches.get(ctx).getValue()));
} }
_pathBranches=trie;
// add new context branches to concurrent map
for (Branch[] branches: path2Branches.values())
{
for (Branch branch : branches)
{
for (ContextHandler context : branch.getContextHandlers())
mapping._contextBranches.put(context, branch.getHandler());
}
}
return mapping;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/*
* @see org.eclipse.jetty.server.server.handler.HandlerCollection#setHandlers(org.eclipse.jetty.server.server.Handler[])
*/
@Override
public void setHandlers(Handler[] handlers)
{
super.setHandlers(handlers);
if (isStarted())
mapContexts();
}
/* ------------------------------------------------------------ */
@Override
protected void doStart() throws Exception
{
mapContexts();
super.doStart();
}
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
*/
@Override @Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{ {
Handler[] handlers = getHandlers(); Handlers handlers = _handlers.get();
if (handlers==null || handlers.length==0) if (handlers==null)
return; return;
Mapping mapping = (Mapping)handlers;
HttpChannelState async = baseRequest.getHttpChannelState(); HttpChannelState async = baseRequest.getHttpChannelState();
if (async.isAsync()) if (async.isAsync())
{ {
ContextHandler context=async.getContextHandler(); ContextHandler context=async.getContextHandler();
if (context!=null) if (context!=null)
{ {
Handler branch = _contextBranches.get(context); Handler branch = mapping._contextBranches.get(context);
if (branch==null) if (branch==null)
context.handle(target,baseRequest,request, response); context.handle(target,baseRequest,request, response);
@ -195,19 +196,19 @@ public class ContextHandlerCollection extends HandlerCollection
} }
} }
// data structure which maps a request to a context; first-best match wins
// { context path => [ context ] }
// }
if (target.startsWith("/")) if (target.startsWith("/"))
{ {
Trie<Map.Entry<String,Branch[]>> pathBranches = mapping._pathBranches;
if (pathBranches==null)
return;
int limit = target.length()-1; int limit = target.length()-1;
while (limit>=0) while (limit>=0)
{ {
// Get best match // Get best match
Map.Entry<String,Branch[]> branches = _pathBranches.getBest(target,1,limit); Map.Entry<String,Branch[]> branches = pathBranches.getBest(target,1,limit);
if (branches==null) if (branches==null)
break; break;
@ -227,10 +228,11 @@ public class ContextHandlerCollection extends HandlerCollection
} }
else else
{ {
// This may not work in all circumstances... but then I think it should never be called if (mapping.getHandlers()==null)
for (int i=0;i<handlers.length;i++) return;
for (int i=0;i<mapping.getHandlers().length;i++)
{ {
handlers[i].handle(target,baseRequest, request, response); mapping.getHandlers()[i].handle(target,baseRequest, request, response);
if ( baseRequest.isHandled()) if ( baseRequest.isHandled())
return; return;
} }
@ -238,11 +240,15 @@ public class ContextHandlerCollection extends HandlerCollection
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Add a context handler. /**
* Adds a context handler.
*
* @param contextPath The context path to add * @param contextPath The context path to add
* @param resourceBase the base (root) Resource * @param resourceBase the base (root) Resource
* @return the ContextHandler just added * @return the ContextHandler just added
* @deprecated Unused convenience method no longer supported.
*/ */
@Deprecated
public ContextHandler addContext(String contextPath,String resourceBase) public ContextHandler addContext(String contextPath,String resourceBase)
{ {
try try
@ -260,22 +266,90 @@ public class ContextHandlerCollection extends HandlerCollection
} }
} }
/* ------------------------------------------------------------ */
/**
* Thread safe deploy of a Handler.
* <p>
* This method is the equivalent of {@link #addHandler(Handler)},
* but its execution is non-block and mutually excluded from all
* other calls to {@link #deployHandler(Handler, Callback)} and
* {@link #undeployHandler(Handler, Callback)}.
* The handler may be added after this call returns.
* </p>
* @param handler the handler to deploy
* @param callback Called after handler has been added
*/
public void deployHandler(Handler handler, Callback callback)
{
if (handler.getServer()!=getServer())
handler.setServer(getServer());
_serializedExecutor.execute(new SerializedExecutor.ErrorHandlingTask()
{
@Override
public void run()
{
addHandler(handler);
callback.succeeded();
}
@Override
public void accept(Throwable throwable)
{
callback.failed(throwable);
}
});
}
/* ------------------------------------------------------------ */
/**
* Thread safe undeploy of a Handler.
* <p>
* This method is the equivalent of {@link #removeHandler(Handler)},
* but its execution is non-block and mutually excluded from all
* other calls to {@link #deployHandler(Handler,Callback)} and
* {@link #undeployHandler(Handler,Callback)}.
* The handler may be removed after this call returns.
* </p>
* @param handler The handler to undeploy
* @param callback Called after handler has been removed
*/
public void undeployHandler(Handler handler, Callback callback)
{
_serializedExecutor.execute(new SerializedExecutor.ErrorHandlingTask()
{
@Override
public void run()
{
removeHandler(handler);
callback.succeeded();
}
@Override
public void accept(Throwable throwable)
{
callback.failed(throwable);
}
});
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* @return The class to use to add new Contexts * @return The class to use to add new Contexts
* @deprecated Unused convenience mechanism not used.
*/ */
@Deprecated
public Class<?> getContextClass() public Class<?> getContextClass()
{ {
return _contextClass; return _contextClass;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* @param contextClass The class to use to add new Contexts * @param contextClass The class to use to add new Contexts
* @deprecated Unused convenience mechanism not used.
*/ */
@Deprecated
public void setContextClass(Class<? extends ContextHandler> contextClass) public void setContextClass(Class<? extends ContextHandler> contextClass)
{ {
if (contextClass ==null || !(ContextHandler.class.isAssignableFrom(contextClass))) if (contextClass ==null || !(ContextHandler.class.isAssignableFrom(contextClass)))
@ -342,5 +416,18 @@ public class ContextHandlerCollection extends HandlerCollection
} }
} }
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private static class Mapping extends Handlers
{
private final Map<ContextHandler,Handler> _contextBranches = new HashMap<>();
private final Trie<Map.Entry<String,Branch[]>> _pathBranches;
private Mapping(Handler[] handlers, int capacity)
{
super(handlers);
_pathBranches = new ArrayTernaryTrie<>(false, capacity);
}
}
} }

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.server.handler;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -47,7 +48,7 @@ import org.eclipse.jetty.util.annotation.ManagedObject;
public class HandlerCollection extends AbstractHandlerContainer public class HandlerCollection extends AbstractHandlerContainer
{ {
private final boolean _mutableWhenRunning; private final boolean _mutableWhenRunning;
private volatile Handler[] _handlers; protected final AtomicReference<Handlers> _handlers = new AtomicReference<>();
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public HandlerCollection() public HandlerCollection()
@ -71,72 +72,93 @@ public class HandlerCollection extends AbstractHandlerContainer
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* @return Returns the handlers. * @return the array of handlers.
*/ */
@Override @Override
@ManagedAttribute(value="Wrapped handlers", readonly=true) @ManagedAttribute(value="Wrapped handlers", readonly=true)
public Handler[] getHandlers() public Handler[] getHandlers()
{ {
return _handlers; Handlers handlers = _handlers.get();
return handlers==null ? null : handlers._handlers;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* @param handlers The handlers to set. * @param handlers the array of handlers to set.
*/ */
public void setHandlers(Handler[] handlers) public void setHandlers(Handler[] handlers)
{ {
if (!_mutableWhenRunning && isStarted()) if (!_mutableWhenRunning && isStarted())
throw new IllegalStateException(STARTED); throw new IllegalStateException(STARTED);
if (handlers!=null) while(true)
{ {
// check for loops if (updateHandlers(_handlers.get(), newHandlers(handlers)))
for (Handler handler:handlers) break;
if (handler == this || (handler instanceof HandlerContainer &&
Arrays.asList(((HandlerContainer)handler).getChildHandlers()).contains(this)))
throw new IllegalStateException("setHandler loop");
// Set server
for (Handler handler:handlers)
if (handler.getServer()!=getServer())
handler.setServer(getServer());
} }
Handler[] old=_handlers;;
_handlers = handlers;
updateBeans(old, handlers);
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** protected Handlers newHandlers(Handler[] handlers)
* @see Handler#handle(String, Request, HttpServletRequest, HttpServletResponse) {
*/ if (handlers==null || handlers.length==0)
return null;
return new Handlers(handlers);
}
/* ------------------------------------------------------------ */
protected boolean updateHandlers(Handlers old, Handlers handlers)
{
if (handlers!=null)
{
// check for loops
for (Handler handler:handlers._handlers)
if (handler == this || (handler instanceof HandlerContainer &&
Arrays.asList(((HandlerContainer)handler).getChildHandlers()).contains(this)))
throw new IllegalStateException("setHandler loop");
// Set server
for (Handler handler:handlers._handlers)
if (handler.getServer()!=getServer())
handler.setServer(getServer());
}
if (_handlers.compareAndSet(old, handlers))
{
Handler[] oldBeans = old == null ? null : old._handlers;
Handler[] newBeans = handlers == null ? null : handlers._handlers;
updateBeans(oldBeans, newBeans);
return true;
}
return false;
}
/* ------------------------------------------------------------ */
@Override @Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException throws IOException, ServletException
{ {
if (_handlers!=null && isStarted()) if (isStarted())
{ {
MultiException mex=null; Handlers handlers = _handlers.get();
if (handlers==null)
return;
for (int i=0;i<_handlers.length;i++) MultiException mex=null;
for (Handler handler : handlers._handlers)
{ {
try try
{ {
_handlers[i].handle(target,baseRequest, request, response); handler.handle(target, baseRequest, request, response);
} }
catch(IOException e) catch (IOException | RuntimeException e)
{ {
throw e; throw e;
} }
catch(RuntimeException e) catch (Exception e)
{ {
throw e; if (mex == null)
} mex = new MultiException();
catch(Exception e)
{
if (mex==null)
mex=new MultiException();
mex.add(e); mex.add(e);
} }
} }
@ -147,37 +169,54 @@ public class HandlerCollection extends AbstractHandlerContainer
else else
throw new ServletException(mex); throw new ServletException(mex);
} }
} }
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/* Add a handler. /**
* Adds a handler.
* This implementation adds the passed handler to the end of the existing collection of handlers. * This implementation adds the passed handler to the end of the existing collection of handlers.
* @see org.eclipse.jetty.server.server.HandlerContainer#addHandler(org.eclipse.jetty.server.server.Handler) * If the handler is already added, it is removed and readded
*/ */
public void addHandler(Handler handler) public void addHandler(Handler handler)
{ {
setHandlers(ArrayUtil.addToArray(getHandlers(), handler, Handler.class)); while(true)
{
Handlers old = _handlers.get();
Handlers handlers = newHandlers(ArrayUtil.addToArray(old==null?null:ArrayUtil.removeFromArray(old._handlers, handler), handler, Handler.class));
if (updateHandlers(old,handlers))
break;
}
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/* Prepend a handler. /**
* Prepends a handler.
* This implementation adds the passed handler to the start of the existing collection of handlers. * This implementation adds the passed handler to the start of the existing collection of handlers.
* @see org.eclipse.jetty.server.server.HandlerContainer#addHandler(org.eclipse.jetty.server.server.Handler)
*/ */
public void prependHandler(Handler handler) public void prependHandler(Handler handler)
{ {
setHandlers(ArrayUtil.prependToArray(handler, getHandlers(), Handler.class)); while(true)
{
Handlers old = _handlers.get();
Handlers handlers = newHandlers(ArrayUtil.prependToArray(handler, old==null?null:old._handlers, Handler.class));
if (updateHandlers(old,handlers))
break;
}
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public void removeHandler(Handler handler) public void removeHandler(Handler handler)
{ {
Handler[] handlers = getHandlers(); while(true)
{
if (handlers!=null && handlers.length>0 ) Handlers old = _handlers.get();
setHandlers(ArrayUtil.removeFromArray(handlers, handler)); if (old==null || old._handlers.length==0)
break;
Handlers handlers = newHandlers(ArrayUtil.removeFromArray(old._handlers, handler));
if (updateHandlers(old,handlers))
break;
}
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -196,10 +235,28 @@ public class HandlerCollection extends AbstractHandlerContainer
{ {
if (!isStopped()) if (!isStopped())
throw new IllegalStateException("!STOPPED"); throw new IllegalStateException("!STOPPED");
Handler[] children=getChildHandlers(); Handler[] children = getChildHandlers();
setHandlers(null); setHandlers(null);
for (Handler child: children) for (Handler child: children)
child.destroy(); child.destroy();
super.destroy(); super.destroy();
} }
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
protected static class Handlers
{
private final Handler[] _handlers;
protected Handlers(Handler[] handlers)
{
this._handlers = handlers;
}
public Handler[] getHandlers()
{
return _handlers;
}
}
} }

View File

@ -22,6 +22,7 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;

View File

@ -18,17 +18,17 @@
package org.eclipse.jetty.server; package org.eclipse.jetty.server;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertNull;
import org.eclipse.jetty.server.handler.ErrorHandler; import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.util.log.StacklessLogging; import org.eclipse.jetty.util.log.StacklessLogging;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertNull;
/** /**
* *
*/ */
@ -82,6 +82,7 @@ public class ProxyConnectionTest
} }
@Test @Test
@Tag("ipv6")
public void testIPv6() throws Exception public void testIPv6() throws Exception
{ {
String response=_connector.getResponse("PROXY UNKNOWN eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 65535 65535\r\n"+ String response=_connector.getResponse("PROXY UNKNOWN eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 65535 65535\r\n"+

View File

@ -18,16 +18,6 @@
package org.eclipse.jetty.server.handler; package org.eclipse.jetty.server.handler;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException; import java.io.IOException;
import javax.servlet.AsyncContext; import javax.servlet.AsyncContext;
@ -41,9 +31,18 @@ import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class ContextHandlerCollectionTest public class ContextHandlerCollectionTest
{ {
@Test @Test
@ -214,7 +213,7 @@ public class ContextHandlerCollectionTest
IsHandledHandler handler = (IsHandledHandler)context.getHandler(); IsHandledHandler handler = (IsHandledHandler)context.getHandler();
context.setVirtualHosts(contextHosts); context.setVirtualHosts(contextHosts);
// trigger this manually; it's supposed to be called when adding the handler // trigger this manually
handlerCollection.mapContexts(); handlerCollection.mapContexts();
for(String host : requestHosts) for(String host : requestHosts)

View File

@ -167,7 +167,7 @@ public class ResourceHandlerTest
_local.getResponse("GET /resource/ HTTP/1.0\r\n\r\n")); _local.getResponse("GET /resource/ HTTP/1.0\r\n\r\n"));
assertThat(response.getStatus(),equalTo(200)); assertThat(response.getStatus(),equalTo(200));
assertThat(response.getContent(),containsString("jetty-dir.css")); assertThat(response.getContent(),containsString("jetty-dir.css"));
assertThat(response.getContent(),containsString("<H1>Directory: /resource/")); assertThat(response.getContent(),containsString("Directory: /resource/"));
assertThat(response.getContent(),containsString("big.txt")); assertThat(response.getContent(),containsString("big.txt"));
assertThat(response.getContent(),containsString("bigger.txt")); assertThat(response.getContent(),containsString("bigger.txt"));
assertThat(response.getContent(),containsString("directory")); assertThat(response.getContent(),containsString("directory"));

View File

@ -23,7 +23,6 @@ import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.UnavailableException; import javax.servlet.UnavailableException;
@ -48,8 +47,7 @@ import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory; import org.eclipse.jetty.util.resource.ResourceFactory;
/**
/**
* The default servlet. * The default servlet.
* <p> * <p>
* This servlet, normally mapped to /, provides the handling for static * This servlet, normally mapped to /, provides the handling for static
@ -79,9 +77,9 @@ import org.eclipse.jetty.util.resource.ResourceFactory;
* *
* gzip If set to true, then static content will be served as * gzip If set to true, then static content will be served as
* gzip content encoded if a matching resource is * gzip content encoded if a matching resource is
* found ending with ".gz" (default false) * found ending with ".gz" (default false)
* (deprecated: use precompressed) * (deprecated: use precompressed)
* *
* precompressed If set to a comma separated list of encoding types (that may be * precompressed If set to a comma separated list of encoding types (that may be
* listed in a requests Accept-Encoding header) to file * listed in a requests Accept-Encoding header) to file
* extension mappings to look for and serve. For example: * extension mappings to look for and serve. For example:
@ -131,10 +129,10 @@ import org.eclipse.jetty.util.resource.ResourceFactory;
public class DefaultServlet extends HttpServlet implements ResourceFactory, WelcomeFactory public class DefaultServlet extends HttpServlet implements ResourceFactory, WelcomeFactory
{ {
public static final String CONTEXT_INIT = "org.eclipse.jetty.servlet.Default."; public static final String CONTEXT_INIT = "org.eclipse.jetty.servlet.Default.";
private static final Logger LOG = Log.getLogger(DefaultServlet.class); private static final Logger LOG = Log.getLogger(DefaultServlet.class);
private static final long serialVersionUID = 4930458713846881193L; private static final long serialVersionUID = 4930458713846881193L;
private final ResourceService _resourceService; private final ResourceService _resourceService;
private ServletContext _servletContext; private ServletContext _servletContext;
@ -165,7 +163,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory, Welc
{ {
this(new ResourceService()); this(new ResourceService());
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@Override @Override
public void init() public void init()
@ -186,7 +184,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory, Welc
_resourceService.setPrecompressedFormats(parsePrecompressedFormats(getInitParameter("precompressed"), getInitBoolean("gzip", false))); _resourceService.setPrecompressedFormats(parsePrecompressedFormats(getInitParameter("precompressed"), getInitBoolean("gzip", false)));
_resourceService.setPathInfoOnly(getInitBoolean("pathInfoOnly",_resourceService.isPathInfoOnly())); _resourceService.setPathInfoOnly(getInitBoolean("pathInfoOnly",_resourceService.isPathInfoOnly()));
_resourceService.setEtags(getInitBoolean("etags",_resourceService.isEtags())); _resourceService.setEtags(getInitBoolean("etags",_resourceService.isEtags()));
if ("exact".equals(getInitParameter("welcomeServlets"))) if ("exact".equals(getInitParameter("welcomeServlets")))
{ {
_welcomeExactServlets=true; _welcomeExactServlets=true;
@ -242,8 +240,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory, Welc
String cc=getInitParameter("cacheControl"); String cc=getInitParameter("cacheControl");
if (cc!=null) if (cc!=null)
_resourceService.setCacheControl(new PreEncodedHttpField(HttpHeader.CACHE_CONTROL,cc)); _resourceService.setCacheControl(new PreEncodedHttpField(HttpHeader.CACHE_CONTROL,cc));
String resourceCache = getInitParameter("resourceCache"); String resourceCache = getInitParameter("resourceCache");
int max_cache_size=getInitInt("maxCacheSize", -2); int max_cache_size=getInitInt("maxCacheSize", -2);
int max_cached_file_size=getInitInt("maxCachedFileSize", -2); int max_cached_file_size=getInitInt("maxCachedFileSize", -2);
@ -286,7 +283,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory, Welc
} }
_resourceService.setContentFactory(contentFactory); _resourceService.setContentFactory(contentFactory);
_resourceService.setWelcomeFactory(this); _resourceService.setWelcomeFactory(this);
List<String> gzip_equivalent_file_extensions = new ArrayList<String>(); List<String> gzip_equivalent_file_extensions = new ArrayList<String>();
String otherGzipExtensions = getInitParameter("otherGzipFileExtensions"); String otherGzipExtensions = getInitParameter("otherGzipFileExtensions");
if (otherGzipExtensions != null) if (otherGzipExtensions != null)

View File

@ -157,10 +157,14 @@ public class DefaultServletTest
defholder.setInitParameter("gzip", "false"); defholder.setInitParameter("gzip", "false");
/* create some content in the docroot */ /* create some content in the docroot */
FS.ensureDirExists(docRoot.resolve("one")); Path one = docRoot.resolve("one");
FS.ensureDirExists(one);
FS.ensureDirExists(docRoot.resolve("two")); FS.ensureDirExists(docRoot.resolve("two"));
FS.ensureDirExists(docRoot.resolve("three")); FS.ensureDirExists(docRoot.resolve("three"));
Path alert = one.resolve("onmouseclick='alert(oops)'");
FS.touch(alert);
/* /*
* Intentionally bad request URI. Sending a non-encoded URI with typically * Intentionally bad request URI. Sending a non-encoded URI with typically
* encoded characters '<', '>', and '"'. * encoded characters '<', '>', and '"'.
@ -172,6 +176,16 @@ public class DefaultServletTest
String body = response.getContent(); String body = response.getContent();
assertThat(body, not(containsString("<script>"))); assertThat(body, not(containsString("<script>")));
req1 = "GET /context/one/;\"onmouseover='alert(document.location)' HTTP/1.0\r\n" +
"\r\n";
rawResponse = connector.getResponse(req1);
response = HttpTester.parseResponse(rawResponse);
body = response.getContent();
assertThat(body, not(containsString(";\"onmouseover")));
} }
@Test @Test

View File

@ -35,7 +35,7 @@ public class UnixSocketClient
{ {
java.io.File path = new java.io.File("/tmp/jetty.sock"); java.io.File path = new java.io.File("/tmp/jetty.sock");
java.io.File content = new java.io.File("/tmp/data.txt"); java.io.File content = new java.io.File("/tmp/data.txt");
String method = "GET"; String method = "GET";
int content_length = 0; int content_length = 0;
String body = null; String body = null;
@ -45,12 +45,12 @@ public class UnixSocketClient
body = IO.readToString(content); body = IO.readToString(content);
content_length = body.length(); content_length = body.length();
} }
String data = method+" / HTTP/1.1\r\n" String data = method + " / HTTP/1.1\r\n"
+ "Host: unixsock\r\n" + "Host: unixsock\r\n"
+ "Content-Length: "+content_length+"\r\n" + "Content-Length: " + content_length + "\r\n"
+ "Connection: close\r\n" + "Connection: close\r\n"
+ "\r\n"; + "\r\n";
if (body!=null) if (body != null)
data += body; data += body;
while (true) while (true)
@ -59,18 +59,18 @@ public class UnixSocketClient
UnixSocketChannel channel = UnixSocketChannel.open(address); UnixSocketChannel channel = UnixSocketChannel.open(address);
System.out.println("connected to " + channel.getRemoteSocketAddress()); System.out.println("connected to " + channel.getRemoteSocketAddress());
PrintWriter w = new PrintWriter(new OutputStreamWriter(Channels.newOutputStream(channel),StandardCharsets.ISO_8859_1)); PrintWriter w = new PrintWriter(new OutputStreamWriter(Channels.newOutputStream(channel), StandardCharsets.ISO_8859_1));
InputStreamReader r = new InputStreamReader(Channels.newInputStream(channel)); InputStreamReader r = new InputStreamReader(Channels.newInputStream(channel));
w.print(data); w.print(data);
w.flush(); w.flush();
CharBuffer result = CharBuffer.allocate(4096); CharBuffer result = CharBuffer.allocate(4096);
String total=""; String total = "";
int l = 0; int l = 0;
while (l>=0) while (l >= 0)
{ {
if (l>0) if (l > 0)
{ {
result.flip(); result.flip();
total += result.toString(); total += result.toString();

View File

@ -71,18 +71,16 @@ public class UnixSocketTest
server = null; server = null;
httpClient = null; httpClient = null;
String unixSocketTmp = System.getProperty("unix.socket.tmp"); String unixSocketTmp = System.getProperty("unix.socket.tmp");
Path unixSocket;
if (StringUtil.isNotBlank(unixSocketTmp)) if (StringUtil.isNotBlank(unixSocketTmp))
unixSocket = Files.createTempFile(Paths.get(unixSocketTmp), "unix", ".sock"); sockFile = Files.createTempFile(Paths.get(unixSocketTmp), "unix", ".sock");
else else
unixSocket = Files.createTempFile("unix", ".sock"); sockFile = Files.createTempFile("unix", ".sock");
if (unixSocket.toAbsolutePath().toString().length() > UnixSocketConnector.MAX_UNIX_SOCKET_PATH_LENGTH) if (sockFile.toAbsolutePath().toString().length() > UnixSocketConnector.MAX_UNIX_SOCKET_PATH_LENGTH)
{ {
Path tmp = Paths.get("/tmp"); Path tmp = Paths.get("/tmp");
assumeTrue(Files.exists(tmp) && Files.isDirectory(tmp)); assumeTrue(Files.exists(tmp) && Files.isDirectory(tmp));
unixSocket = Files.createTempFile(tmp, "unix", ".sock"); sockFile = Files.createTempFile(tmp, "unix", ".sock");
} }
sockFile = unixSocket;
assertTrue(Files.deleteIfExists(sockFile), "temp sock file cannot be deleted"); assertTrue(Files.deleteIfExists(sockFile), "temp sock file cannot be deleted");
} }

View File

@ -42,111 +42,111 @@ public class JnrTest
serverChannel.configureBlocking(false); serverChannel.configureBlocking(false);
serverChannel.socket().bind(address); serverChannel.socket().bind(address);
serverChannel.register(serverSelector, SelectionKey.OP_ACCEPT, "SERVER"); serverChannel.register(serverSelector, SelectionKey.OP_ACCEPT, "SERVER");
System.err.printf("serverChannel=%s,%n",serverChannel); System.err.printf("serverChannel=%s,%n", serverChannel);
UnixSocketChannel client = UnixSocketChannel.open( address ); UnixSocketChannel client = UnixSocketChannel.open(address);
Selector clientSelector = NativeSelectorProvider.getInstance().openSelector(); Selector clientSelector = NativeSelectorProvider.getInstance().openSelector();
client.configureBlocking(false); client.configureBlocking(false);
SelectionKey clientKey = client.register(clientSelector,0,"client"); SelectionKey clientKey = client.register(clientSelector, 0, "client");
System.err.printf("client=%s connected=%b pending=%b%n",client,client.isConnected(),client.isConnectionPending()); System.err.printf("client=%s connected=%b pending=%b%n", client, client.isConnected(), client.isConnectionPending());
int selected = serverSelector.select(); int selected = serverSelector.select();
System.err.printf("serverSelected=%d %s%n",selected,serverSelector.selectedKeys()); System.err.printf("serverSelected=%d %s%n", selected, serverSelector.selectedKeys());
SelectionKey key = serverSelector.selectedKeys().iterator().next(); SelectionKey key = serverSelector.selectedKeys().iterator().next();
serverSelector.selectedKeys().clear(); serverSelector.selectedKeys().clear();
System.err.printf("key=%s/%s c=%b a=%b r=%b w=%b%n",key,key.attachment(),key.isConnectable(),key.isAcceptable(),key.isReadable(),key.isWritable()); System.err.printf("key=%s/%s c=%b a=%b r=%b w=%b%n", key, key.attachment(), key.isConnectable(), key.isAcceptable(), key.isReadable(), key.isWritable());
UnixSocketChannel server = serverChannel.accept(); UnixSocketChannel server = serverChannel.accept();
server.configureBlocking(false); server.configureBlocking(false);
SelectionKey serverKey = server.register(serverSelector, SelectionKey.OP_READ, "server"); SelectionKey serverKey = server.register(serverSelector, SelectionKey.OP_READ, "server");
System.err.printf("server=%s connected=%b pending=%b%n",server,server.isConnected(),server.isConnectionPending()); System.err.printf("server=%s key=%s connected=%b pending=%b%n", server, serverKey, server.isConnected(), server.isConnectionPending());
selected = serverSelector.selectNow(); selected = serverSelector.selectNow();
System.err.printf("serverSelected=%d %s%n",selected,serverSelector.selectedKeys()); System.err.printf("serverSelected=%d %s%n", selected, serverSelector.selectedKeys());
ByteBuffer buffer = ByteBuffer.allocate(32768); ByteBuffer buffer = ByteBuffer.allocate(32768);
buffer.clear(); buffer.clear();
int read = server.read(buffer); int read = server.read(buffer);
buffer.flip(); buffer.flip();
System.err.printf("server read=%d%n",read); System.err.printf("server read=%d%n", read);
selected = clientSelector.selectNow(); selected = clientSelector.selectNow();
System.err.printf("clientSelected=%d %s%n",selected,clientSelector.selectedKeys()); System.err.printf("clientSelected=%d %s%n", selected, clientSelector.selectedKeys());
int wrote = client.write(ByteBuffer.wrap("Hello".getBytes(StandardCharsets.ISO_8859_1))); int wrote = client.write(ByteBuffer.wrap("Hello".getBytes(StandardCharsets.ISO_8859_1)));
System.err.printf("client wrote=%d%n",wrote); System.err.printf("client wrote=%d%n", wrote);
selected = serverSelector.selectNow(); selected = serverSelector.selectNow();
System.err.printf("serverSelected=%d %s%n",selected,serverSelector.selectedKeys()); System.err.printf("serverSelected=%d %s%n", selected, serverSelector.selectedKeys());
key = serverSelector.selectedKeys().iterator().next(); key = serverSelector.selectedKeys().iterator().next();
serverSelector.selectedKeys().clear(); serverSelector.selectedKeys().clear();
System.err.printf("key=%s/%s c=%b a=%b r=%b w=%b ch=%s%n",key,key.attachment(),key.isConnectable(),key.isAcceptable(),key.isReadable(),key.isWritable(),key.channel()); System.err.printf("key=%s/%s c=%b a=%b r=%b w=%b ch=%s%n", key, key.attachment(), key.isConnectable(), key.isAcceptable(), key.isReadable(), key.isWritable(), key.channel());
buffer.clear(); buffer.clear();
read = server.read(buffer); read = server.read(buffer);
buffer.flip(); buffer.flip();
System.err.printf("server read=%d '%s'%n",read,new String(buffer.array(),0,buffer.limit(),StandardCharsets.ISO_8859_1)); System.err.printf("server read=%d '%s'%n", read, new String(buffer.array(), 0, buffer.limit(), StandardCharsets.ISO_8859_1));
selected = clientSelector.selectNow(); selected = clientSelector.selectNow();
System.err.printf("clientSelected=%d %s%n",selected,clientSelector.selectedKeys()); System.err.printf("clientSelected=%d %s%n", selected, clientSelector.selectedKeys());
wrote = server.write(ByteBuffer.wrap("Ciao!".getBytes(StandardCharsets.ISO_8859_1))); wrote = server.write(ByteBuffer.wrap("Ciao!".getBytes(StandardCharsets.ISO_8859_1)));
System.err.printf("server wrote=%d%n",wrote); System.err.printf("server wrote=%d%n", wrote);
selected = clientSelector.selectNow(); selected = clientSelector.selectNow();
System.err.printf("clientSelected=%d %s%n",selected,clientSelector.selectedKeys()); System.err.printf("clientSelected=%d %s%n", selected, clientSelector.selectedKeys());
clientKey.interestOps(SelectionKey.OP_READ); clientKey.interestOps(SelectionKey.OP_READ);
selected = clientSelector.selectNow(); selected = clientSelector.selectNow();
System.err.printf("clientSelected=%d %s%n",selected,clientSelector.selectedKeys()); System.err.printf("clientSelected=%d %s%n", selected, clientSelector.selectedKeys());
key = clientSelector.selectedKeys().iterator().next(); key = clientSelector.selectedKeys().iterator().next();
clientSelector.selectedKeys().clear(); clientSelector.selectedKeys().clear();
System.err.printf("key=%s/%s c=%b a=%b r=%b w=%b ch=%s%n",key,key.attachment(),key.isConnectable(),key.isAcceptable(),key.isReadable(),key.isWritable(),key.channel()); System.err.printf("key=%s/%s c=%b a=%b r=%b w=%b ch=%s%n", key, key.attachment(), key.isConnectable(), key.isAcceptable(), key.isReadable(), key.isWritable(), key.channel());
buffer.clear(); buffer.clear();
read = client.read(buffer); read = client.read(buffer);
buffer.flip(); buffer.flip();
System.err.printf("client read=%d '%s'%n",read,new String(buffer.array(),0,buffer.limit(),StandardCharsets.ISO_8859_1)); System.err.printf("client read=%d '%s'%n", read, new String(buffer.array(), 0, buffer.limit(), StandardCharsets.ISO_8859_1));
System.err.println("So far so good.... now it gets strange..."); System.err.println("So far so good.... now it gets strange...");
// Let's write until flow control hit // Let's write until flow control hit
int size = buffer.capacity(); int size = buffer.capacity();
Arrays.fill(buffer.array(),0,size,(byte)'X'); Arrays.fill(buffer.array(), 0, size, (byte)'X');
long written = 0; long written = 0;
while(true) while (true)
{ {
buffer.position(0).limit(size); buffer.position(0).limit(size);
wrote = server.write(buffer); wrote = server.write(buffer);
System.err.printf("server wrote %d/%d remaining=%d%n",wrote,size,buffer.remaining()); System.err.printf("server wrote %d/%d remaining=%d%n", wrote, size, buffer.remaining());
if (buffer.remaining()!=(size-wrote)) if (buffer.remaining() != (size - wrote))
System.err.printf("BUG!!!!!!!!!!!!!!!!%n"); System.err.printf("BUG!!!!!!!!!!!!!!!!%n");
if (wrote==0) if (wrote == 0)
break; break;
written+=wrote; written += wrote;
} }
System.err.printf("server wrote %d before flow control%n",written); System.err.printf("server wrote %d before flow control%n", written);
selected = clientSelector.selectNow(); selected = clientSelector.selectNow();
System.err.printf("clientSelected=%d %s%n",selected,clientSelector.selectedKeys()); System.err.printf("clientSelected=%d %s%n", selected, clientSelector.selectedKeys());
key = clientSelector.selectedKeys().iterator().next(); key = clientSelector.selectedKeys().iterator().next();
clientSelector.selectedKeys().clear(); clientSelector.selectedKeys().clear();
System.err.printf("key=%s/%s c=%b a=%b r=%b w=%b ch=%s%n",key,key.attachment(),key.isConnectable(),key.isAcceptable(),key.isReadable(),key.isWritable(),key.channel()); System.err.printf("key=%s/%s c=%b a=%b r=%b w=%b ch=%s%n", key, key.attachment(), key.isConnectable(), key.isAcceptable(), key.isReadable(), key.isWritable(), key.channel());
buffer.clear(); buffer.clear();
buffer.limit(32); buffer.limit(32);
read = client.read(buffer); read = client.read(buffer);
buffer.flip(); buffer.flip();
System.err.printf("client read=%d '%s'%n",read,new String(buffer.array(),0,buffer.limit(),StandardCharsets.ISO_8859_1)); System.err.printf("client read=%d '%s'%n", read, new String(buffer.array(), 0, buffer.limit(), StandardCharsets.ISO_8859_1));
server.close(); server.close();
client.close(); client.close();

View File

@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd"> <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
<Configure id="unixSocketConnector" class="org.eclipse.jetty.server.ServerConnector"> <Configure id="unixSocketConnector" class="org.eclipse.jetty.unixsocket.UnixSocketConnector">
<Call name="addFirstConnectionFactory"> <Call name="addFirstConnectionFactory">
<Arg> <Arg>
<New class="org.eclipse.jetty.server.ProxyConnectionFactory"/> <New class="org.eclipse.jetty.server.ProxyConnectionFactory"/>

View File

@ -19,19 +19,19 @@ server
etc/jetty-unixsocket.xml etc/jetty-unixsocket.xml
[files] [files]
maven://com.github.jnr/jnr-unixsocket/0.8|lib/jnr/jnr-unixsocket-0.8.jar maven://com.github.jnr/jnr-unixsocket/0.20|lib/jnr/jnr-unixsocket-0.20.jar
maven://com.github.jnr/jnr-ffi/2.0.3|lib/jnr/jnr-ffi-2.0.3.jar maven://com.github.jnr/jnr-ffi/2.1.9|lib/jnr/jnr-ffi-2.1.9.jar
maven://com.github.jnr/jffi/1.2.9|lib/jnr/jffi-1.2.9.jar maven://com.github.jnr/jffi/1.2.17|lib/jnr/jffi-1.2.17.jar
maven://com.github.jnr/jffi/1.2.9/jar/native|lib/jnr/jffi-1.2.9-native.jar maven://com.github.jnr/jffi/1.2.16/jar/native|lib/jnr/jffi-1.2.16-native.jar
maven://org.ow2.asm/asm/5.0.1|lib/jnr/asm-5.0.1.jar maven://org.ow2.asm/asm/7.0|lib/jnr/asm-7.0.jar
maven://org.ow2.asm/asm-commons/5.0.1|lib/jnr/asm-commons-5.0.1.jar maven://org.ow2.asm/asm-commons/7.0|lib/jnr/asm-commons-7.0.jar
maven://org.ow2.asm/asm-analysis/5.0.3|lib/jnr/asm-analysis-5.0.3.jar maven://org.ow2.asm/asm-analysis/7.0|lib/jnr/asm-analysis-7.0.jar
maven://org.ow2.asm/asm-tree/5.0.3|lib/jnr/asm-tree-5.0.3.jar maven://org.ow2.asm/asm-tree/7.0|lib/jnr/asm-tree-7.0.jar
maven://org.ow2.asm/asm-util/5.0.3|lib/jnr/asm-util-5.0.3.jar maven://org.ow2.asm/asm-util/7.0|lib/jnr/asm-util-7.0.jar
maven://com.github.jnr/jnr-x86asm/1.0.2|lib/jnr/jnr-x86asm-1.0.2.jar maven://com.github.jnr/jnr-x86asm/1.0.2|lib/jnr/jnr-x86asm-1.0.2.jar
maven://com.github.jnr/jnr-constants/0.8.7|lib/jnr/jnr-constants-0.8.7.jar maven://com.github.jnr/jnr-constants/0.9.11|lib/jnr/jnr-constants-0.9.11.jar
maven://com.github.jnr/jnr-enxio/0.9|lib/jnr/jnr-enxio-0.9.jar maven://com.github.jnr/jnr-enxio/0.18|lib/jnr/jnr-enxio-0.18.jar
maven://com.github.jnr/jnr-posix/3.0.12|lib/jnr/jnr-posix-3.0.12.jar maven://com.github.jnr/jnr-posix/3.0.46|lib/jnr/jnr-posix-3.0.46.jar
[lib] [lib]
lib/jetty-unixsocket-${jetty.version}.jar lib/jetty-unixsocket-${jetty.version}.jar

View File

@ -53,6 +53,9 @@ import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.util.thread.Scheduler;
/**
* <p>A server-side connector for UNIX sockets.</p>
*/
@ManagedObject("Connector using UNIX Socket") @ManagedObject("Connector using UNIX Socket")
public class UnixSocketConnector extends AbstractConnector public class UnixSocketConnector extends AbstractConnector
{ {
@ -67,10 +70,9 @@ public class UnixSocketConnector extends AbstractConnector
private volatile boolean _reuseAddress = true; private volatile boolean _reuseAddress = true;
/** /**
* HTTP Server Connection. * <p>Constructs a UnixSocketConnector with the default configuration.</p>
* <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the only factory.</p>
* *
* @param server The {@link Server} this connector will accept connection for. * @param server the {@link Server} this connector will accept connections for.
*/ */
public UnixSocketConnector(@Name("server") Server server) public UnixSocketConnector(@Name("server") Server server)
{ {
@ -78,11 +80,10 @@ public class UnixSocketConnector extends AbstractConnector
} }
/** /**
* HTTP Server Connection. * <p>Constructs a UnixSocketConnector with the given number of selectors</p>
* <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the only factory.</p>
* *
* @param server The {@link Server} this connector will accept connection for. * @param server the {@link Server} this connector will accept connections for.
* @param selectors the number of selector threads, or &lt;=0 for a default value. Selectors notice and schedule established connection that can make IO progress. * @param selectors the number of selectors, or &lt;=0 for a default value.
*/ */
public UnixSocketConnector(@Name("server") Server server, @Name("selectors") int selectors) public UnixSocketConnector(@Name("server") Server server, @Name("selectors") int selectors)
{ {
@ -90,11 +91,10 @@ public class UnixSocketConnector extends AbstractConnector
} }
/** /**
* Generic Server Connection with default configuration. * <p>Constructs a UnixSocketConnector with the given ConnectionFactories.</p>
* <p>Construct a Server Connector with the passed Connection factories.</p>
* *
* @param server The {@link Server} this connector will accept connection for. * @param server the {@link Server} this connector will accept connections for.
* @param factories Zero or more {@link ConnectionFactory} instances used to create and configure connections. * @param factories zero or more {@link ConnectionFactory} instances used to create and configure connections.
*/ */
public UnixSocketConnector(@Name("server") Server server, @Name("factories") ConnectionFactory... factories) public UnixSocketConnector(@Name("server") Server server, @Name("factories") ConnectionFactory... factories)
{ {
@ -102,12 +102,11 @@ public class UnixSocketConnector extends AbstractConnector
} }
/** /**
* HTTP Server Connection. * <p>Constructs a UnixSocketConnector with the given selectors and ConnectionFactories.</p>
* <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the only factory.</p>
* *
* @param server The {@link Server} this connector will accept connection for. * @param server the {@link Server} this connector will accept connections for.
* @param selectors the number of selector threads, or &lt;=0 for a default value. Selectors notice and schedule established connection that can make IO progress. * @param selectors the number of selectors, or &lt;=0 for a default value.
* @param factories Zero or more {@link ConnectionFactory} instances used to create and configure connections. * @param factories zero or more {@link ConnectionFactory} instances used to create and configure connections.
*/ */
public UnixSocketConnector(@Name("server") Server server, @Name("selectors") int selectors, @Name("factories") ConnectionFactory... factories) public UnixSocketConnector(@Name("server") Server server, @Name("selectors") int selectors, @Name("factories") ConnectionFactory... factories)
{ {
@ -115,12 +114,10 @@ public class UnixSocketConnector extends AbstractConnector
} }
/** /**
* HTTP Server Connection. * <p>Constructs a UnixSocketConnector with the given SslContextFactory.</p>
* <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the primary protocol</p>.
* *
* @param server The {@link Server} this connector will accept connection for. * @param server the {@link Server} this connector will accept connections for.
* @param sslContextFactory If non null, then a {@link SslConnectionFactory} is instantiated and prepended to the * @param sslContextFactory when non null a {@link SslConnectionFactory} prepended to the other ConnectionFactories
* list of HTTP Connection Factory.
*/ */
public UnixSocketConnector(@Name("server") Server server, @Name("sslContextFactory") SslContextFactory.Server sslContextFactory) public UnixSocketConnector(@Name("server") Server server, @Name("sslContextFactory") SslContextFactory.Server sslContextFactory)
{ {
@ -128,13 +125,11 @@ public class UnixSocketConnector extends AbstractConnector
} }
/** /**
* HTTP Server Connection. * <p>Constructs a UnixSocketConnector with the given selectors and SslContextFactory.</p>.
* <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the primary protocol</p>.
* *
* @param server The {@link Server} this connector will accept connection for. * @param server the {@link Server} this connector will accept connections for.
* @param sslContextFactory If non null, then a {@link SslConnectionFactory} is instantiated and prepended to the * @param sslContextFactory when non null a {@link SslConnectionFactory} prepended to the other ConnectionFactories
* list of HTTP Connection Factory. * @param selectors the number of selectors, or &lt;=0 for a default value.
* @param selectors the number of selector threads, or &lt;=0 for a default value. Selectors notice and schedule established connection that can make IO progress.
*/ */
public UnixSocketConnector(@Name("server") Server server, @Name("selectors") int selectors, @Name("sslContextFactory") SslContextFactory.Server sslContextFactory) public UnixSocketConnector(@Name("server") Server server, @Name("selectors") int selectors, @Name("sslContextFactory") SslContextFactory.Server sslContextFactory)
{ {
@ -142,12 +137,11 @@ public class UnixSocketConnector extends AbstractConnector
} }
/** /**
* Generic SSL Server Connection. * <p>Constructs a UnixSocketConnector with the given SslContextFactory and ConnectionFactories.</p>.
* *
* @param server The {@link Server} this connector will accept connection for. * @param server the {@link Server} this connector will accept connections for.
* @param sslContextFactory If non null, then a {@link SslConnectionFactory} is instantiated and prepended to the * @param sslContextFactory when non null a {@link SslConnectionFactory} prepended to the other ConnectionFactories
* list of ConnectionFactories, with the first factory being the default protocol for the SslConnectionFactory. * @param factories zero or more {@link ConnectionFactory} instances used to create and configure connections.
* @param factories Zero or more {@link ConnectionFactory} instances used to create and configure connections.
*/ */
public UnixSocketConnector(@Name("server") Server server, @Name("sslContextFactory") SslContextFactory.Server sslContextFactory, @Name("factories") ConnectionFactory... factories) public UnixSocketConnector(@Name("server") Server server, @Name("sslContextFactory") SslContextFactory.Server sslContextFactory, @Name("factories") ConnectionFactory... factories)
{ {
@ -155,15 +149,14 @@ public class UnixSocketConnector extends AbstractConnector
} }
/** /**
* Generic Server Connection. * <p>Constructs a UnixSocketConnector with the given parameters.</p>.
* *
* @param server The server this connector will be accept connection for. * @param server the {@link Server} this connector will accept connections for.
* @param executor An executor used to run tasks for handling requests, acceptors and selectors. * @param executor the executor that runs tasks for handling requests, acceptors and selectors.
* If null then use the servers executor * @param scheduler the scheduler used to schedule timed tasks.
* @param scheduler A scheduler used to schedule timeouts. If null then use the servers scheduler * @param bufferPool the ByteBufferPool used to allocate buffers.
* @param bufferPool A ByteBuffer pool used to allocate buffers. If null then create a private pool with default configuration. * @param selectors the number of selectors, or &lt;=0 for a default value.
* @param selectors the number of selector threads, or &lt;=0 for a default value(1). Selectors notice and schedule established connection that can make IO progress. * @param factories zero or more {@link ConnectionFactory} instances used to create and configure connections.
* @param factories Zero or more {@link ConnectionFactory} instances used to create and configure connections.
*/ */
public UnixSocketConnector(@Name("server") Server server, @Name("executor") Executor executor, @Name("scheduler") Scheduler scheduler, @Name("bufferPool") ByteBufferPool bufferPool, @Name("selectors") int selectors, @Name("factories") ConnectionFactory... factories) public UnixSocketConnector(@Name("server") Server server, @Name("executor") Executor executor, @Name("scheduler") Scheduler scheduler, @Name("bufferPool") ByteBufferPool bufferPool, @Name("selectors") int selectors, @Name("factories") ConnectionFactory... factories)
{ {
@ -172,7 +165,7 @@ public class UnixSocketConnector extends AbstractConnector
addBean(_manager, true); addBean(_manager, true);
} }
@ManagedAttribute @ManagedAttribute("The UNIX socket file name")
public String getUnixSocket() public String getUnixSocket()
{ {
return _unixSocket; return _unixSocket;
@ -295,7 +288,7 @@ public class UnixSocketConnector extends AbstractConnector
return _acceptChannel; return _acceptChannel;
} }
protected UnixSocketEndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key) throws IOException protected UnixSocketEndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key)
{ {
return new UnixSocketEndPoint((UnixSocketChannel)channel, selector, key, getScheduler()); return new UnixSocketEndPoint((UnixSocketChannel)channel, selector, key, getScheduler());
} }
@ -321,6 +314,7 @@ public class UnixSocketConnector extends AbstractConnector
* @return whether the server socket reuses addresses * @return whether the server socket reuses addresses
* @see ServerSocket#getReuseAddress() * @see ServerSocket#getReuseAddress()
*/ */
@ManagedAttribute("Whether the server socket reuses addresses")
public boolean getReuseAddress() public boolean getReuseAddress()
{ {
return _reuseAddress; return _reuseAddress;
@ -338,9 +332,7 @@ public class UnixSocketConnector extends AbstractConnector
@Override @Override
public String toString() public String toString()
{ {
return String.format("%s{%s}", return String.format("%s{%s}", super.toString(), _unixSocket);
super.toString(),
_unixSocket);
} }
protected class UnixSocketConnectorManager extends SelectorManager protected class UnixSocketConnectorManager extends SelectorManager
@ -363,15 +355,15 @@ public class UnixSocketConnector extends AbstractConnector
} }
@Override @Override
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey selectionKey) throws IOException protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey selectionKey)
{ {
UnixSocketEndPoint endp = UnixSocketConnector.this.newEndPoint(channel, selector, selectionKey); UnixSocketEndPoint endPoint = UnixSocketConnector.this.newEndPoint(channel, selector, selectionKey);
endp.setIdleTimeout(getIdleTimeout()); endPoint.setIdleTimeout(getIdleTimeout());
return endp; return endPoint;
} }
@Override @Override
public Connection newConnection(SelectableChannel channel, EndPoint endpoint, Object attachment) throws IOException public Connection newConnection(SelectableChannel channel, EndPoint endpoint, Object attachment)
{ {
return getDefaultConnectionFactory().newConnection(UnixSocketConnector.this, endpoint); return getDefaultConnectionFactory().newConnection(UnixSocketConnector.this, endpoint);
} }

View File

@ -25,7 +25,6 @@ import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Date; import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -38,32 +37,31 @@ import org.eclipse.jetty.unixsocket.server.UnixSocketConnector;
public class UnixSocketProxyServer public class UnixSocketProxyServer
{ {
public static void main (String... args) throws Exception public static void main(String... args) throws Exception
{ {
Server server = new Server(); Server server = new Server();
HttpConnectionFactory http = new HttpConnectionFactory(); HttpConnectionFactory http = new HttpConnectionFactory();
ProxyConnectionFactory proxy = new ProxyConnectionFactory(http.getProtocol()); ProxyConnectionFactory proxy = new ProxyConnectionFactory(http.getProtocol());
UnixSocketConnector connector = new UnixSocketConnector(server,proxy,http); UnixSocketConnector connector = new UnixSocketConnector(server, proxy, http);
server.addConnector(connector); server.addConnector(connector);
Path socket = Paths.get(connector.getUnixSocket()); Path socket = Paths.get(connector.getUnixSocket());
if (Files.exists(socket)) if (Files.exists(socket))
Files.delete(socket); Files.delete(socket);
server.setHandler(new AbstractHandler.ErrorDispatchHandler() server.setHandler(new AbstractHandler.ErrorDispatchHandler()
{ {
@Override @Override
protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
throws IOException, ServletException
{ {
int l = 0; int l = 0;
if (request.getContentLength()!=0) if (request.getContentLength() != 0)
{ {
InputStream in = request.getInputStream(); InputStream in = request.getInputStream();
byte[] buffer = new byte[4096]; byte[] buffer = new byte[4096];
int r = 0; int r = 0;
while (r>=0) while (r >= 0)
{ {
l += r; l += r;
r = in.read(buffer); r = in.read(buffer);
@ -71,21 +69,21 @@ public class UnixSocketProxyServer
} }
baseRequest.setHandled(true); baseRequest.setHandled(true);
response.setStatus(200); response.setStatus(200);
response.getWriter().write("Hello World "+new Date() + "\r\n"); response.getWriter().write("Hello World " + new Date() + "\r\n");
response.getWriter().write("remote="+request.getRemoteAddr()+":"+request.getRemotePort()+"\r\n"); response.getWriter().write("remote=" + request.getRemoteAddr() + ":" + request.getRemotePort() + "\r\n");
response.getWriter().write("local ="+request.getLocalAddr()+":"+request.getLocalPort()+"\r\n"); response.getWriter().write("local =" + request.getLocalAddr() + ":" + request.getLocalPort() + "\r\n");
response.getWriter().write("read ="+l+"\r\n"); response.getWriter().write("read =" + l + "\r\n");
} }
}); });
server.start(); server.start();
while (true) while (true)
{ {
Thread.sleep(5000); Thread.sleep(5000);
connector.dumpStdErr(); connector.dumpStdErr();
} }
// server.join(); // server.join();
} }
} }

View File

@ -36,12 +36,12 @@ import org.eclipse.jetty.unixsocket.server.UnixSocketConnector;
public class UnixSocketServer public class UnixSocketServer
{ {
public static void main (String... args) throws Exception public static void main(String... args) throws Exception
{ {
Server server = new Server(); Server server = new Server();
HttpConnectionFactory http = new HttpConnectionFactory(); HttpConnectionFactory http = new HttpConnectionFactory();
UnixSocketConnector connector = new UnixSocketConnector(server,http); UnixSocketConnector connector = new UnixSocketConnector(server, http);
server.addConnector(connector); server.addConnector(connector);
Path socket = Paths.get(connector.getUnixSocket()); Path socket = Paths.get(connector.getUnixSocket());
@ -51,16 +51,15 @@ public class UnixSocketServer
server.setHandler(new AbstractHandler.ErrorDispatchHandler() server.setHandler(new AbstractHandler.ErrorDispatchHandler()
{ {
@Override @Override
protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
throws IOException
{ {
int l = 0; int l = 0;
if (request.getContentLength()!=0) if (request.getContentLength() != 0)
{ {
InputStream in = request.getInputStream(); InputStream in = request.getInputStream();
byte[] buffer = new byte[4096]; byte[] buffer = new byte[4096];
int r = 0; int r = 0;
while (r>=0) while (r >= 0)
{ {
l += r; l += r;
r = in.read(buffer); r = in.read(buffer);
@ -68,10 +67,10 @@ public class UnixSocketServer
} }
baseRequest.setHandled(true); baseRequest.setHandled(true);
response.setStatus(200); response.setStatus(200);
response.getWriter().write("Hello World "+new Date() + "\r\n"); response.getWriter().write("Hello World " + new Date() + "\r\n");
response.getWriter().write("remote="+request.getRemoteAddr()+":"+request.getRemotePort()+"\r\n"); response.getWriter().write("remote=" + request.getRemoteAddr() + ":" + request.getRemotePort() + "\r\n");
response.getWriter().write("local ="+request.getLocalAddr()+":"+request.getLocalPort()+"\r\n"); response.getWriter().write("local =" + request.getLocalAddr() + ":" + request.getLocalPort() + "\r\n");
response.getWriter().write("read ="+l+"\r\n"); response.getWriter().write("read =" + l + "\r\n");
} }
}); });

View File

@ -253,6 +253,7 @@ public class BufferUtil
} }
} }
/* ------------------------------------------------------------ */
/** /**
* @param buf the buffer to check * @param buf the buffer to check
* @return true if buf is equal to EMPTY_BUFFER * @return true if buf is equal to EMPTY_BUFFER
@ -274,6 +275,36 @@ public class BufferUtil
return buf == null || buf.remaining() == 0; return buf == null || buf.remaining() == 0;
} }
/* ------------------------------------------------------------ */
/** Check for an empty or null buffers.
* @param buf the buffer to check
* @return true if the buffer is null or empty.
*/
public static boolean isEmpty(ByteBuffer[] buf)
{
if (buf==null || buf.length==0)
return true;
for (ByteBuffer b : buf)
if (b!=null && b.hasRemaining())
return false;
return true;
}
/* ------------------------------------------------------------ */
/** Get the remaining bytes in 0 or more buffers.
* @param buf the buffers to check
* @return number of bytes remaining in all buffers.
*/
public static long remaining(ByteBuffer... buf)
{
long remaining = 0;
if (buf!=null)
for (ByteBuffer b : buf)
if (b!=null)
remaining += b.remaining();
return remaining;
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Check for a non null and non empty buffer. /** Check for a non null and non empty buffer.
* @param buf the buffer to check * @param buf the buffer to check

View File

@ -310,6 +310,7 @@ public interface Callback extends Invocable
return callback.getInvocationType(); return callback.getInvocationType();
} }
} }
/** /**
* <p>A CompletableFuture that is also a Callback.</p> * <p>A CompletableFuture that is also a Callback.</p>
*/ */

View File

@ -31,15 +31,17 @@ import java.nio.channels.ReadableByteChannel;
import java.nio.file.Path; import java.nio.file.Path;
import java.text.DateFormat; import java.text.DateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List;
import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.UrlEncoded;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
@ -102,7 +104,7 @@ public abstract class Resource implements ResourceFactory, Closeable
* Construct a resource from a url. * Construct a resource from a url.
* @param url the url for which to make the resource * @param url the url for which to make the resource
* @param useCaches true enables URLConnection caching if applicable to the type of resource * @param useCaches true enables URLConnection caching if applicable to the type of resource
* @return * @return a new resource from the given URL
*/ */
static Resource newResource(URL url, boolean useCaches) static Resource newResource(URL url, boolean useCaches)
{ {
@ -143,8 +145,7 @@ public abstract class Resource implements ResourceFactory, Closeable
* @throws MalformedURLException Problem accessing URI * @throws MalformedURLException Problem accessing URI
* @return A Resource object. * @return A Resource object.
*/ */
public static Resource newResource(String resource) public static Resource newResource(String resource) throws IOException
throws MalformedURLException, IOException
{ {
return newResource(resource, __defaultUseCaches); return newResource(resource, __defaultUseCaches);
} }
@ -156,10 +157,9 @@ public abstract class Resource implements ResourceFactory, Closeable
* @return A Resource object. * @return A Resource object.
* @throws MalformedURLException Problem accessing URI * @throws MalformedURLException Problem accessing URI
*/ */
public static Resource newResource(String resource, boolean useCaches) public static Resource newResource(String resource, boolean useCaches) throws IOException
throws MalformedURLException, IOException
{ {
URL url=null; URL url;
try try
{ {
// Try to format as a URL? // Try to format as a URL?
@ -222,8 +222,7 @@ public abstract class Resource implements ResourceFactory, Closeable
* @return The new Resource * @return The new Resource
* @throws IOException Problem accessing resource. * @throws IOException Problem accessing resource.
*/ */
public static Resource newSystemResource(String resource) public static Resource newSystemResource(String resource) throws IOException
throws IOException
{ {
URL url=null; URL url=null;
// Try to format as a URL? // Try to format as a URL?
@ -444,7 +443,6 @@ public abstract class Resource implements ResourceFactory, Closeable
* <p> * <p>
* This method is essentially an alias for {@link #addPath(String)}, but without checked exceptions. * This method is essentially an alias for {@link #addPath(String)}, but without checked exceptions.
* This method satisfied the {@link ResourceFactory} interface. * This method satisfied the {@link ResourceFactory} interface.
* @see org.eclipse.jetty.util.resource.ResourceFactory#getResource(java.lang.String)
*/ */
@Override @Override
public Resource getResource(String path) public Resource getResource(String path)
@ -501,65 +499,228 @@ public abstract class Resource implements ResourceFactory, Closeable
* @return String of HTML * @return String of HTML
* @throws IOException if unable to get the list of resources as HTML * @throws IOException if unable to get the list of resources as HTML
*/ */
public String getListHTML(String base,boolean parent) public String getListHTML(String base, boolean parent) throws IOException
throws IOException {
return getListHTML(base, parent, null);
}
/** Get the resource list as a HTML directory listing.
* @param base The base URL
* @param parent True if the parent directory should be included
* @param query query params
* @return String of HTML
*/
public String getListHTML(String base, boolean parent, String query) throws IOException
{ {
base=URIUtil.canonicalPath(base); base=URIUtil.canonicalPath(base);
if (base==null || !isDirectory()) if (base==null || !isDirectory())
return null; return null;
String[] ls = list();
if (ls==null)
return null;
Arrays.sort(ls);
String decodedBase = URIUtil.decodePath(base);
String title = "Directory: "+deTag(decodedBase);
StringBuilder buf=new StringBuilder(4096); String[] rawListing = list();
buf.append("<HTML><HEAD>"); if (rawListing == null)
buf.append("<LINK HREF=\"").append("jetty-dir.css").append("\" REL=\"stylesheet\" TYPE=\"text/css\"/><TITLE>"); {
return null;
}
boolean sortOrderAscending = true;
String sortColumn = "N"; // name (or "M" for Last Modified, or "S" for Size)
// check for query
if (query != null)
{
MultiMap<String> params = new MultiMap<>();
UrlEncoded.decodeUtf8To(query, 0, query.length(), params);
String paramO = params.getString("O");
String paramC = params.getString("C");
if (StringUtil.isNotBlank(paramO))
{
if (paramO.equals("A"))
{
sortOrderAscending = true;
}
else if (paramO.equals("D"))
{
sortOrderAscending = false;
}
}
if (StringUtil.isNotBlank(paramC))
{
if (paramC.equals("N") || paramC.equals("M") || paramC.equals("S"))
{
sortColumn = paramC;
}
}
}
// Gather up entries
List<Resource> items = new ArrayList<>();
for (String l : rawListing)
{
Resource item = addPath(l);
items.add(item);
}
// Perform sort
if (sortColumn.equals("M"))
{
items.sort(ResourceCollators.byLastModified(sortOrderAscending));
}
else if (sortColumn.equals("S"))
{
items.sort(ResourceCollators.bySize(sortOrderAscending));
}
else
{
items.sort(ResourceCollators.byName(sortOrderAscending));
}
String decodedBase = URIUtil.decodePath(base);
String title = "Directory: " + deTag(decodedBase);
StringBuilder buf = new StringBuilder(4096);
// Doctype Declaration (HTML5)
buf.append("<!DOCTYPE html>\n");
buf.append("<html lang=\"en\">\n");
// HTML Header
buf.append("<head>\n");
buf.append("<meta charset=\"utf-8\">\n");
buf.append("<link href=\"jetty-dir.css\" rel=\"stylesheet\" />\n");
buf.append("<title>");
buf.append(title); buf.append(title);
buf.append("</TITLE></HEAD><BODY>\n<H1>"); buf.append("</title>\n");
buf.append(title); buf.append("</head>\n");
buf.append("</H1>\n<TABLE BORDER=0>\n");
// HTML Body
buf.append("<body>\n");
buf.append("<h1 class=\"title\">").append(title).append("</h1>\n");
// HTML Table
final String ARROW_DOWN = "&nbsp; &#8681;";
final String ARROW_UP = "&nbsp; &#8679;";
String arrow;
String order;
buf.append("<table class=\"listing\">\n");
buf.append("<thead>\n");
arrow = "";
order = "A";
if (sortColumn.equals("N"))
{
if(sortOrderAscending)
{
order = "D";
arrow = ARROW_UP;
}
else
{
order = "A";
arrow = ARROW_DOWN;
}
}
buf.append("<tr><th class=\"name\"><a href=\"?C=N&O=").append(order).append("\">");
buf.append("Name").append(arrow);
buf.append("</a></th>");
arrow = "";
order = "A";
if (sortColumn.equals("M"))
{
if(sortOrderAscending)
{
order = "D";
arrow = ARROW_UP;
}
else
{
order = "A";
arrow = ARROW_DOWN;
}
}
buf.append("<th class=\"lastmodified\"><a href=\"?C=M&O=").append(order).append("\">");
buf.append("Last Modified").append(arrow);
buf.append("</a></th>");
arrow = "";
order = "A";
if (sortColumn.equals("S"))
{
if(sortOrderAscending)
{
order = "D";
arrow = ARROW_UP;
}
else
{
order = "A";
arrow = ARROW_DOWN;
}
}
buf.append("<th class=\"size\"><a href=\"?C=S&O=").append(order).append("\">");
buf.append("Size").append(arrow);
buf.append("</a></th></tr>\n");
buf.append("</thead>\n");
buf.append("<tbody>\n");
String encodedBase = hrefEncodeURI(base);
if (parent) if (parent)
{ {
buf.append("<TR><TD><A HREF=\""); // Name
buf.append(URIUtil.addEncodedPaths(base,"../")); buf.append("<tr><td class=\"name\"><a href=\"");
buf.append("\">Parent Directory</A></TD><TD></TD><TD></TD></TR>\n"); buf.append(URIUtil.addPaths(encodedBase,"../"));
buf.append("\">Parent Directory</a></td>");
// Last Modified
buf.append("<td class=\"lastmodified\">-</td>");
// Size
buf.append("<td>-</td>");
buf.append("</tr>\n");
} }
String encodedBase = hrefEncodeURI(base);
DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
DateFormat.MEDIUM); DateFormat.MEDIUM);
for (int i=0 ; i< ls.length ; i++) for (Resource item: items)
{ {
Resource item = addPath(ls[i]); String name = item.getName();
int slashIdx = name.lastIndexOf('/');
buf.append("\n<TR><TD><A HREF=\""); if (slashIdx != -1)
String path=URIUtil.addEncodedPaths(encodedBase,URIUtil.encodePath(ls[i])); {
name = name.substring(slashIdx + 1);
}
if (item.isDirectory() && !name.endsWith("/"))
{
name += URIUtil.SLASH;
}
// Name
buf.append("<tr><td class=\"name\"><a href=\"");
String path=URIUtil.addEncodedPaths(encodedBase,URIUtil.encodePath(name));
buf.append(path); buf.append(path);
if (item.isDirectory() && !path.endsWith("/"))
buf.append(URIUtil.SLASH);
// URIUtil.encodePath(buf,path);
buf.append("\">"); buf.append("\">");
buf.append(deTag(ls[i])); buf.append(deTag(name));
buf.append("&nbsp;"); buf.append("&nbsp;");
buf.append("</A></TD><TD ALIGN=right>"); buf.append("</a></td>");
buf.append(item.length());
buf.append(" bytes&nbsp;</TD><TD>"); // Last Modified
buf.append("<td class=\"lastmodified\">");
buf.append(dfmt.format(new Date(item.lastModified()))); buf.append(dfmt.format(new Date(item.lastModified())));
buf.append("</TD></TR>"); buf.append("</td>");
// Size
buf.append("<td class=\"size\">");
buf.append(String.format("%,d", item.length()));
buf.append(" bytes&nbsp;</td></tr>\n");
} }
buf.append("</TABLE>\n"); buf.append("</tbody>\n");
buf.append("</BODY></HTML>\n"); buf.append("</table>\n");
buf.append("</body></html>\n");
return buf.toString(); return buf.toString();
} }
@ -599,21 +760,21 @@ public abstract class Resource implements ResourceFactory, Closeable
char c=raw.charAt(i); char c=raw.charAt(i);
switch(c) switch(c)
{ {
case '"': case '"':
buf.append("%22"); buf.append("%22");
continue; break;
case '\'': case '\'':
buf.append("%27"); buf.append("%27");
continue; break;
case '<': case '<':
buf.append("%3C"); buf.append("%3C");
continue; break;
case '>': case '>':
buf.append("%3E"); buf.append("%3E");
continue; break;
default: default:
buf.append(c); buf.append(c);
continue; break;
} }
} }

View File

@ -0,0 +1,91 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.resource;
import java.text.Collator;
import java.util.Collections;
import java.util.Comparator;
import java.util.Locale;
public class ResourceCollators
{
private static Comparator<? super Resource> BY_NAME_ASCENDING =
new Comparator<>()
{
private final Collator collator = Collator.getInstance(Locale.ENGLISH);
@Override
public int compare(Resource o1, Resource o2)
{
return collator.compare(o1.getName(), o2.getName());
}
};
private static Comparator<? super Resource> BY_NAME_DESCENDING =
Collections.reverseOrder(BY_NAME_ASCENDING);
private static Comparator<? super Resource> BY_LAST_MODIFIED_ASCENDING =
Comparator.comparingLong(Resource::lastModified);
private static Comparator<? super Resource> BY_LAST_MODIFIED_DESCENDING =
Collections.reverseOrder(BY_LAST_MODIFIED_ASCENDING);
private static Comparator<? super Resource> BY_SIZE_ASCENDING =
Comparator.comparingLong(Resource::length);
private static Comparator<? super Resource> BY_SIZE_DESCENDING =
Collections.reverseOrder(BY_SIZE_ASCENDING);
public static Comparator<? super Resource> byLastModified(boolean sortOrderAscending)
{
if (sortOrderAscending)
{
return BY_LAST_MODIFIED_ASCENDING;
}
else
{
return BY_LAST_MODIFIED_DESCENDING;
}
}
public static Comparator<? super Resource> byName(boolean sortOrderAscending)
{
if (sortOrderAscending)
{
return BY_NAME_ASCENDING;
}
else
{
return BY_NAME_DESCENDING;
}
}
public static Comparator<? super Resource> bySize(boolean sortOrderAscending)
{
if (sortOrderAscending)
{
return BY_SIZE_ASCENDING;
}
else
{
return BY_SIZE_DESCENDING;
}
}
}

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.util.thread; package org.eclipse.jetty.util.thread;
import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -146,6 +147,9 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP
@Override @Override
protected void doStop() throws Exception protected void doStop() throws Exception
{ {
if (LOG.isDebugEnabled())
LOG.debug("Stopping {}", this);
removeBean(_tryExecutor); removeBean(_tryExecutor);
_tryExecutor = TryExecutor.NO_TRY; _tryExecutor = TryExecutor.NO_TRY;
@ -163,11 +167,13 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP
for (int i = _threadsStarted.get(); i-- > 0; ) for (int i = _threadsStarted.get(); i-- > 0; )
jobs.offer(noop); jobs.offer(noop);
// try to jobs complete naturally for half our stop time // try to let jobs complete naturally for half our stop time
long stopby = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2; long stopby = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2;
for (Thread thread : _threads) for (Thread thread : _threads)
{ {
long canwait = TimeUnit.NANOSECONDS.toMillis(stopby - System.nanoTime()); long canwait = TimeUnit.NANOSECONDS.toMillis(stopby - System.nanoTime());
if (LOG.isDebugEnabled())
LOG.debug("Waiting for {} for {}", thread, canwait);
if (canwait > 0) if (canwait > 0)
thread.join(canwait); thread.join(canwait);
} }
@ -177,13 +183,19 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP
// interrupt remaining threads // interrupt remaining threads
if (_threadsStarted.get() > 0) if (_threadsStarted.get() > 0)
for (Thread thread : _threads) for (Thread thread : _threads)
{
if (LOG.isDebugEnabled())
LOG.debug("Interrupting {}", thread);
thread.interrupt(); thread.interrupt();
}
// wait again for the other half of our stop time // wait again for the other half of our stop time
stopby = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2; stopby = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2;
for (Thread thread : _threads) for (Thread thread : _threads)
{ {
long canwait = TimeUnit.NANOSECONDS.toMillis(stopby - System.nanoTime()); long canwait = TimeUnit.NANOSECONDS.toMillis(stopby - System.nanoTime());
if (LOG.isDebugEnabled())
LOG.debug("Waiting for {} for {}", thread, canwait);
if (canwait > 0) if (canwait > 0)
thread.join(canwait); thread.join(canwait);
} }
@ -213,6 +225,25 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP
} }
} }
// Close any un-executed jobs
while (!_jobs.isEmpty())
{
Runnable job = _jobs.poll();
if (job instanceof Closeable)
{
try
{
((Closeable)job).close();
}
catch (Throwable t)
{
LOG.warn(t);
}
}
else if (job != noop)
LOG.warn("Stopped without executing or closing {}", job);
}
if (_budget!=null) if (_budget!=null)
_budget.reset(); _budget.reset();
@ -535,6 +566,8 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP
thread.setDaemon(isDaemon()); thread.setDaemon(isDaemon());
thread.setPriority(getThreadsPriority()); thread.setPriority(getThreadsPriority());
thread.setName(_name + "-" + thread.getId()); thread.setName(_name + "-" + thread.getId());
if (LOG.isDebugEnabled())
LOG.debug("Starting {}", thread);
_threads.add(thread); _threads.add(thread);
_lastShrink.set(System.nanoTime()); _lastShrink.set(System.nanoTime());
thread.start(); thread.start();
@ -651,105 +684,93 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP
_tryExecutor); _tryExecutor);
} }
private Runnable idleJobPoll() throws InterruptedException
{
return _jobs.poll(_idleTimeout, TimeUnit.MILLISECONDS);
}
private Runnable _runnable = new Runnable() private Runnable _runnable = new Runnable()
{ {
@Override @Override
public void run() public void run()
{ {
boolean shrink = false; boolean idle = false;
boolean ignore = false;
try try
{ {
Runnable job = _jobs.poll(); Runnable job = _jobs.poll();
if (job != null && _threadsIdle.get() == 0) if (job != null && _threadsIdle.get() == 0)
{
startThreads(1); startThreads(1);
}
loop: while (true)
while (isRunning())
{ {
// Job loop if (job == null)
while (job != null && isRunning())
{ {
if (!idle)
{
idle = true;
_threadsIdle.incrementAndGet();
}
if (_idleTimeout <= 0)
job = _jobs.take();
else
{
// maybe we should shrink?
int size = _threadsStarted.get();
if (size > _minThreads)
{
long last = _lastShrink.get();
long now = System.nanoTime();
if (last == 0 || (now - last) > TimeUnit.MILLISECONDS.toNanos(_idleTimeout))
{
if (_lastShrink.compareAndSet(last, now))
break;
}
}
job = _jobs.poll(_idleTimeout, TimeUnit.MILLISECONDS);
}
}
// run job
if (job != null)
{
if (idle)
{
idle = false;
if (_threadsIdle.decrementAndGet() == 0)
startThreads(1);
}
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("run {}", job); LOG.debug("run {}", job);
runJob(job); runJob(job);
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("ran {}", job); LOG.debug("ran {}", job);
if (Thread.interrupted())
{ // Clear interrupted status
ignore = true; Thread.interrupted();
break loop;
}
job = _jobs.poll();
} }
// Idle loop if (!isRunning())
try break;
{
_threadsIdle.incrementAndGet();
while (isRunning() && job == null) job = _jobs.poll();
{
if (_idleTimeout <= 0)
job = _jobs.take();
else
{
// maybe we should shrink?
final int size = _threadsStarted.get();
if (size > _minThreads)
{
long last = _lastShrink.get();
long now = System.nanoTime();
if (last == 0 || (now - last) > TimeUnit.MILLISECONDS.toNanos(_idleTimeout))
{
if (_lastShrink.compareAndSet(last, now) && _threadsStarted.compareAndSet(size, size - 1))
{
shrink = true;
break loop;
}
}
}
job = idleJobPoll();
}
}
}
finally
{
if (_threadsIdle.decrementAndGet() == 0)
{
startThreads(1);
}
}
} }
} }
catch (InterruptedException e) catch (InterruptedException e)
{ {
ignore = true;
LOG.ignore(e); LOG.ignore(e);
} }
catch (Throwable e) catch (Throwable e)
{ {
LOG.warn(e); LOG.warn(String.format("Unexpected thread death: %s in %s", this, QueuedThreadPool.this), e);
} }
finally finally
{ {
if (!shrink && isRunning()) if (idle)
{ _threadsIdle.decrementAndGet();
if (!ignore)
LOG.warn("Unexpected thread death: {} in {}", this, QueuedThreadPool.this);
// This is an unexpected thread death!
if (_threadsStarted.decrementAndGet() < getMaxThreads())
startThreads(1);
}
removeThread(Thread.currentThread()); removeThread(Thread.currentThread());
if (_threadsStarted.decrementAndGet() < getMinThreads())
startThreads(1);
} }
} }
}; };

View File

@ -0,0 +1,111 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.thread;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import org.eclipse.jetty.util.log.Log;
/**
* An executor than ensurers serial execution of submitted tasks.
* <p>
* Callers of this execute will never block in the executor, but they may
* be required to either execute the task they submit or tasks submitted
* by other threads whilst they are executing tasks.
* </p>
* <p>
* This class was inspired by the public domain class
* <a href="https://github.com/jroper/reactive-streams-servlet/blob/master/reactive-streams-servlet/src/main/java/org/reactivestreams/servlet/NonBlockingMutexExecutor.java">NonBlockingMutexExecutor</a>
* </p>
*/
public class SerializedExecutor implements Executor
{
private final AtomicReference<Link> _tail = new AtomicReference<>();
@Override
public void execute(Runnable task)
{
Link link = new Link(task);
Link lastButOne = _tail.getAndSet(link);
if (lastButOne==null)
run(link);
else
lastButOne._next.lazySet(link);
}
protected void onError(Runnable task, Throwable t)
{
if (task instanceof ErrorHandlingTask)
((ErrorHandlingTask)task).accept(t);
Log.getLogger(task.getClass()).warn(t);
}
private void run(Link link)
{
while(link!=null)
{
try
{
link._task.run();
}
catch (Throwable t)
{
onError(link._task, t);
}
finally
{
// Are we the current the last Link?
if (_tail.compareAndSet(link, null))
link = null;
else
{
// not the last task, so its next link will eventually be set
Link next = link._next.get();
while (next == null)
{
Thread.yield(); // Thread.onSpinWait();
next = link._next.get();
}
link = next;
}
}
}
}
private class Link
{
private final Runnable _task;
private final AtomicReference<Link> _next = new AtomicReference<>();
public Link(Runnable task)
{
_task = task;
}
}
/**
* Error handling task
* <p>If a submitted task implements this interface, it will be passed
* any exceptions thrown when running the task.</p>
*/
public interface ErrorHandlingTask extends Runnable, Consumer<Throwable>
{}
}

View File

@ -1,19 +1,38 @@
body body {
{
background-color: #FFFFFF; background-color: #FFFFFF;
margin: 10px; margin: 10px;
padding: 5px; padding: 5px;
font-family: sans-serif;
} }
h1 h1.title {
{
text-shadow: #000000 -1px -1px 1px; text-shadow: #000000 -1px -1px 1px;
color: #FC390E; color: #FC390E;
font-weight: bold; font-weight: bold;
} }
a table.listing {
{ border: 0px;
}
thead a {
color: blue;
}
thead th {
border-bottom: black 1px solid;
}
.name, .lastmodified {
text-align: left;
padding-right: 15px;
}
.size {
text-align: right;
}
a {
color: #7036be; color: #7036be;
font-weight: bold; font-weight: bold;
font-style: normal; font-style: normal;
@ -21,10 +40,9 @@ a
font-size:inherit; font-size:inherit;
} }
td td {
{
font-style: italic; font-style: italic;
padding: 2px 15px 2px 0px; padding: 2px;
} }

View File

@ -18,22 +18,30 @@
package org.eclipse.jetty.util.thread; package org.eclipse.jetty.util.thread;
import org.eclipse.jetty.util.log.StacklessLogging; import java.io.Closeable;
import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool; import java.io.IOException;
import org.junit.jupiter.api.Test;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class QueuedThreadPoolTest extends AbstractThreadPoolTest public class QueuedThreadPoolTest extends AbstractThreadPoolTest
{ {
private static final Logger LOG = Log.getLogger(QueuedThreadPoolTest.class);
private final AtomicInteger _jobs=new AtomicInteger(); private final AtomicInteger _jobs=new AtomicInteger();
private class RunningJob implements Runnable private class RunningJob implements Runnable
@ -41,6 +49,7 @@ public class QueuedThreadPoolTest extends AbstractThreadPoolTest
private final CountDownLatch _run = new CountDownLatch(1); private final CountDownLatch _run = new CountDownLatch(1);
private final CountDownLatch _stopping = new CountDownLatch(1); private final CountDownLatch _stopping = new CountDownLatch(1);
private final CountDownLatch _stopped = new CountDownLatch(1); private final CountDownLatch _stopped = new CountDownLatch(1);
@Override @Override
public void run() public void run()
{ {
@ -51,7 +60,7 @@ public class QueuedThreadPoolTest extends AbstractThreadPoolTest
} }
catch(Exception e) catch(Exception e)
{ {
e.printStackTrace(); LOG.debug(e);
} }
finally finally
{ {
@ -69,6 +78,17 @@ public class QueuedThreadPoolTest extends AbstractThreadPoolTest
} }
} }
private class CloseableJob extends RunningJob implements Closeable
{
private final CountDownLatch _closed = new CountDownLatch(1);
@Override
public void close() throws IOException
{
_closed.countDown();
}
}
@Test @Test
public void testThreadPool() throws Exception public void testThreadPool() throws Exception
{ {
@ -146,6 +166,58 @@ public class QueuedThreadPoolTest extends AbstractThreadPoolTest
waitForIdle(tp,2); waitForIdle(tp,2);
} }
@Test
public void testLifeCycleStop() throws Exception
{
QueuedThreadPool tp= new QueuedThreadPool();
tp.setName("TestPool");
tp.setMinThreads(1);
tp.setMaxThreads(2);
tp.setIdleTimeout(900);
tp.setStopTimeout(500);
tp.setThreadsPriority(Thread.NORM_PRIORITY-1);
tp.start();
// min threads started
waitForThreads(tp,1);
waitForIdle(tp,1);
// Run job0 and job1
RunningJob job0=new RunningJob();
RunningJob job1=new RunningJob();
tp.execute(job0);
tp.execute(job1);
// Add a more jobs (which should not be run)
RunningJob job2=new RunningJob();
CloseableJob job3=new CloseableJob();
RunningJob job4=new RunningJob();
tp.execute(job2);
tp.execute(job3);
tp.execute(job4);
// Wait until the first 2 start running
waitForThreads(tp,2);
waitForIdle(tp,0);
// Queue should be empty after thread pool is stopped
tp.stop();
assertThat(tp.getQueue().size(), is(0));
// First 2 jobs closed by InterruptedException
assertThat(job0._stopped.await(200, TimeUnit.MILLISECONDS), is(true));
assertThat(job1._stopped.await(200, TimeUnit.MILLISECONDS), is(true));
// Verify RunningJobs in the queue have not been run
assertThat(job2._run.await(200, TimeUnit.MILLISECONDS), is(false));
assertThat(job4._run.await(200, TimeUnit.MILLISECONDS), is(false));
// Verify ClosableJobs have not been run but have been closed
assertThat(job4._run.await(200, TimeUnit.MILLISECONDS), is(false));
assertThat(job3._closed.await(200, TimeUnit.MILLISECONDS), is(true));
}
@Test @Test
public void testShrink() throws Exception public void testShrink() throws Exception
{ {

View File

@ -0,0 +1,92 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.thread;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class SerializedExecutorTest
{
@Test
public void test() throws Exception
{
int threads = 64;
int loops = 1000;
int depth = 100;
AtomicInteger ran = new AtomicInteger();
AtomicBoolean running = new AtomicBoolean();
SerializedExecutor executor = new SerializedExecutor();
CountDownLatch start = new CountDownLatch(1);
CountDownLatch stop = new CountDownLatch(threads);
Random random = new Random();
for (int t = threads; t-- > 0; )
{
new Thread(() ->
{
try
{
start.await();
for (int l = loops; l-- > 0; )
{
final AtomicInteger d = new AtomicInteger(depth);
executor.execute(new Runnable()
{
@Override
public void run()
{
ran.incrementAndGet();
if (!running.compareAndSet(false, true))
throw new IllegalStateException();
if (d.decrementAndGet() > 0)
executor.execute(this);
if (!running.compareAndSet(true, false))
throw new IllegalStateException();
}
});
Thread.sleep(random.nextInt(5));
}
}
catch (Throwable th)
{
th.printStackTrace();
}
finally
{
stop.countDown();
}
}).start();
}
start.countDown();
assertTrue(stop.await(30, TimeUnit.SECONDS));
assertThat(ran.get(), Matchers.is(threads * loops * depth));
}
}

View File

@ -130,6 +130,12 @@ public class UpgradeHttpServletRequest implements HttpServletRequest
attributes.put(name, httpRequest.getAttribute(name)); attributes.put(name, httpRequest.getAttribute(name));
} }
Enumeration<Locale> localeElements = httpRequest.getLocales();
while (localeElements.hasMoreElements())
{
locales.add(localeElements.nextElement());
}
localAddress = InetSocketAddress.createUnresolved(httpRequest.getLocalAddr(), httpRequest.getLocalPort()); localAddress = InetSocketAddress.createUnresolved(httpRequest.getLocalAddr(), httpRequest.getLocalPort());
localName = httpRequest.getLocalName(); localName = httpRequest.getLocalName();
remoteAddress = InetSocketAddress.createUnresolved(httpRequest.getRemoteAddr(), httpRequest.getRemotePort()); remoteAddress = InetSocketAddress.createUnresolved(httpRequest.getRemoteAddr(), httpRequest.getRemotePort());

19
pom.xml
View File

@ -47,7 +47,7 @@
<jetty.surefire.argLine>-Dfile.encoding=UTF-8 -Duser.language=en -Duser.region=US -showversion -Xmx1g -Xms1g -Xlog:gc:stderr:time,level,tags</jetty.surefire.argLine> <jetty.surefire.argLine>-Dfile.encoding=UTF-8 -Duser.language=en -Duser.region=US -showversion -Xmx1g -Xms1g -Xlog:gc:stderr:time,level,tags</jetty.surefire.argLine>
<!-- some maven plugins versions --> <!-- some maven plugins versions -->
<maven.surefire.version>3.0.0-M2</maven.surefire.version> <maven.surefire.version>3.0.0-M3</maven.surefire.version>
<maven.compiler.plugin.version>3.8.0</maven.compiler.plugin.version> <maven.compiler.plugin.version>3.8.0</maven.compiler.plugin.version>
<maven.dependency.plugin.version>3.1.1</maven.dependency.plugin.version> <maven.dependency.plugin.version>3.1.1</maven.dependency.plugin.version>
<maven.resources.plugin.version>3.1.0</maven.resources.plugin.version> <maven.resources.plugin.version>3.1.0</maven.resources.plugin.version>
@ -1203,9 +1203,20 @@
</profile> </profile>
<profile> <profile>
<id>ci</id> <id>ci</id>
<modules> <build>
<module>aggregates/jetty-all</module> <pluginManagement>
</modules> <plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven.surefire.version}</version>
<configuration>
<excludedGroups>ipv6</excludedGroups>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile> </profile>
<profile> <profile>
<id>update-version</id> <id>update-version</id>

View File

@ -141,7 +141,7 @@ if proceedyn "Are you sure you want to release using above? (y/N)" n; then
# This is equivalent to 'mvn release:prepare' # This is equivalent to 'mvn release:prepare'
if proceedyn "Update project.versions for $VER_RELEASE? (Y/n)" y; then if proceedyn "Update project.versions for $VER_RELEASE? (Y/n)" y; then
mvn org.codehaus.mojo:versions-maven-plugin:2.5:set \ mvn org.codehaus.mojo:versions-maven-plugin:2.7:set \
-DoldVersion="$VER_CURRENT" \ -DoldVersion="$VER_CURRENT" \
-DnewVersion="$VER_RELEASE" \ -DnewVersion="$VER_RELEASE" \
-DprocessAllModules=true -DprocessAllModules=true

View File

@ -80,6 +80,12 @@
<artifactId>jetty-test-helper</artifactId> <artifactId>jetty-test-helper</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-unixsocket-client</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -19,6 +19,8 @@
package org.eclipse.jetty.tests.distribution; package org.eclipse.jetty.tests.distribution;
import java.io.File; import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -27,6 +29,8 @@ import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
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.unixsocket.client.HttpClientTransportOverUnixSockets;
import org.eclipse.jetty.util.StringUtil;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnJre; import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.condition.JRE;
@ -204,4 +208,53 @@ public class DistributionTests extends AbstractDistributionTest
assertThat(response.getContentAsString(), not(containsString("<%"))); assertThat(response.getContentAsString(), not(containsString("<%")));
} }
} }
@Test
public void unixSocket() throws Exception
{
Path sockFile;
String unixSocketTmp = System.getProperty("unix.socket.tmp");
if( StringUtil.isNotBlank(unixSocketTmp))
{
sockFile = Files.createTempFile(Paths.get(unixSocketTmp), "unix", ".sock");
} else {
sockFile = Files.createTempFile("unix", ".sock");
}
assertTrue(Files.deleteIfExists(sockFile), "temp sock file cannot be deleted");
String jettyVersion = System.getProperty("jettyVersion");
DistributionTester distribution = DistributionTester.Builder.newInstance()
.jettyVersion(jettyVersion)
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
.build();
String[] args1 = {
"--create-startd",
"--add-to-start=unixsocket-http,deploy,jsp",
"--approve-all-licenses"
};
try (DistributionTester.Run run1 = distribution.start(args1))
{
assertTrue(run1.awaitFor(5, TimeUnit.SECONDS));
assertEquals(0, run1.getExitValue());
File war = distribution.resolveArtifact("org.eclipse.jetty.tests:test-simple-webapp:war:" + jettyVersion);
distribution.installWarFile(war, "test");
try (DistributionTester.Run run2 = distribution.start("jetty.unixsocket="+sockFile.toString()))
{
assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS));
startHttpClient(() -> new HttpClient(new HttpClientTransportOverUnixSockets(sockFile.toString())));
ContentResponse response = client.GET("http://localhost/test/index.jsp");
assertEquals(HttpStatus.OK_200, response.getStatus());
assertThat(response.getContentAsString(), containsString("Hello"));
assertThat(response.getContentAsString(), not(containsString("<%")));
}
} finally {
Files.deleteIfExists(sockFile);
}
}
} }

View File

@ -76,7 +76,6 @@ import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.log.StacklessLogging; import org.eclipse.jetty.util.log.StacklessLogging;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Tag;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.provider.ArgumentsSource;
@ -394,12 +393,8 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
@ParameterizedTest @ParameterizedTest
@ArgumentsSource(TransportProvider.class) @ArgumentsSource(TransportProvider.class)
@Tag("Unstable") @Tag("Unstable")
@Disabled // TODO fix this test! #2243
public void testAsyncWriteClosed(Transport transport) throws Exception public void testAsyncWriteClosed(Transport transport) throws Exception
{ {
// TODO work out why this test fails for UNIX_SOCKET
Assumptions.assumeFalse(transport==Transport.UNIX_SOCKET);
init(transport); init(transport);
String text = "Now is the winter of our discontent. How Now Brown Cow. The quick brown fox jumped over the lazy dog.\n"; String text = "Now is the winter of our discontent. How Now Brown Cow. The quick brown fox jumped over the lazy dog.\n";
@ -465,8 +460,6 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
@ArgumentsSource(TransportProvider.class) @ArgumentsSource(TransportProvider.class)
public void testAsyncWriteLessThanContentLengthFlushed(Transport transport) throws Exception public void testAsyncWriteLessThanContentLengthFlushed(Transport transport) throws Exception
{ {
// TODO work out why this test fails for UNIX_SOCKET
Assumptions.assumeFalse(transport==Transport.UNIX_SOCKET);
init(transport); init(transport);
CountDownLatch complete = new CountDownLatch(1); CountDownLatch complete = new CountDownLatch(1);

View File

@ -180,7 +180,7 @@ public class HttpClientLoadTest extends AbstractTest<HttpClientLoadTest.LoadTran
for (String failure : failures) for (String failure : failures)
logger.info("FAILED: {}", failure); logger.info("FAILED: {}", failure);
assertTrue(failures.isEmpty(),failures.toString()); assertTrue(failures.isEmpty(), failures.toString());
} }
private void test(final CountDownLatch latch, final List<String> failures) private void test(final CountDownLatch latch, final List<String> failures)
@ -366,19 +366,18 @@ public class HttpClientLoadTest extends AbstractTest<HttpClientLoadTest.LoadTran
} }
@Override @Override
public Connector newServerConnector( Server server) public Connector newServerConnector(Server server)
{ {
int selectors = Math.min(1, ProcessorUtils.availableProcessors() / 2);
ByteBufferPool byteBufferPool = new ArrayByteBufferPool();
byteBufferPool = new LeakTrackingByteBufferPool(byteBufferPool);
if (transport == Transport.UNIX_SOCKET) if (transport == Transport.UNIX_SOCKET)
{ {
UnixSocketConnector unixSocketConnector = new UnixSocketConnector(server, provideServerConnectionFactory(transport)); UnixSocketConnector unixSocketConnector = new UnixSocketConnector(server, null, null, byteBufferPool, selectors, provideServerConnectionFactory(transport));
unixSocketConnector.setUnixSocket(sockFile.toString()); unixSocketConnector.setUnixSocket(sockFile.toString());
return unixSocketConnector; return unixSocketConnector;
} }
int cores = ProcessorUtils.availableProcessors(); return new ServerConnector(server, null, null, byteBufferPool, 1, selectors, provideServerConnectionFactory(transport));
ByteBufferPool byteBufferPool = new ArrayByteBufferPool();
byteBufferPool = new LeakTrackingByteBufferPool(byteBufferPool);
return new ServerConnector(server, null, null, byteBufferPool,
1, Math.min(1, cores / 2), provideServerConnectionFactory(transport));
} }
@Override @Override

View File

@ -97,7 +97,7 @@ public class RoundRobinConnectionPoolTest extends AbstractTest<TransportScenario
int expected = remotePorts.get(base); int expected = remotePorts.get(base);
int candidate = remotePorts.get(i); int candidate = remotePorts.get(i);
assertThat(scenario.client.dump() + System.lineSeparator() + remotePorts.toString(), expected, Matchers.equalTo(candidate)); assertThat(scenario.client.dump() + System.lineSeparator() + remotePorts.toString(), expected, Matchers.equalTo(candidate));
if (i > 0) if (transport != Transport.UNIX_SOCKET && i > 0)
assertThat(remotePorts.get(i - 1), Matchers.not(Matchers.equalTo(candidate))); assertThat(remotePorts.get(i - 1), Matchers.not(Matchers.equalTo(candidate)));
} }
} }
@ -187,7 +187,7 @@ public class RoundRobinConnectionPoolTest extends AbstractTest<TransportScenario
int expected = remotePorts.get(base); int expected = remotePorts.get(base);
int candidate = remotePorts.get(i); int candidate = remotePorts.get(i);
assertThat(scenario.client.dump() + System.lineSeparator() + remotePorts.toString(), expected, Matchers.equalTo(candidate)); assertThat(scenario.client.dump() + System.lineSeparator() + remotePorts.toString(), expected, Matchers.equalTo(candidate));
if (i > 0) if (transport != Transport.UNIX_SOCKET && i > 0)
assertThat(remotePorts.get(i - 1), Matchers.not(Matchers.equalTo(candidate))); assertThat(remotePorts.get(i - 1), Matchers.not(Matchers.equalTo(candidate)));
} }
} }

View File

@ -20,10 +20,10 @@ package org.eclipse.jetty.http.client;
import java.util.Arrays; import java.util.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsProvider;
@ -35,21 +35,16 @@ public class TransportProvider implements ArgumentsProvider
String transports = System.getProperty(Transport.class.getName()); String transports = System.getProperty(Transport.class.getName());
if (!StringUtil.isBlank(transports)) if (!StringUtil.isBlank(transports))
{ return Arrays.stream(transports.split("\\s*,\\s*")).map(Transport::valueOf);
return Arrays.stream(transports.split("\\s*,\\s*"))
.map(Transport::valueOf);
}
// TODO #2014 too many test failures, don't test unix socket client for now. if (OS.LINUX.isCurrentOs())
// if (OS.IS_UNIX) return Arrays.stream(Transport.values());
// return Transport.values();
return EnumSet.complementOf(EnumSet.of(Transport.UNIX_SOCKET)) return EnumSet.complementOf(EnumSet.of(Transport.UNIX_SOCKET)).stream();
.stream();
} }
@Override @Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception public Stream<? extends Arguments> provideArguments(ExtensionContext context)
{ {
return getActiveTransports().map(Arguments::of); return getActiveTransports().map(Arguments::of);
} }

View File

@ -22,6 +22,7 @@ import java.io.IOException;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -60,11 +61,14 @@ import org.eclipse.jetty.unixsocket.client.HttpClientTransportOverUnixSockets;
import org.eclipse.jetty.unixsocket.server.UnixSocketConnector; import org.eclipse.jetty.unixsocket.server.UnixSocketConnector;
import org.eclipse.jetty.util.BlockingArrayQueue; import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.SocketAddressResolver; import org.eclipse.jetty.util.SocketAddressResolver;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.QueuedThreadPool;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
public class TransportScenario public class TransportScenario
{ {
private static final Logger LOG = Log.getLogger(TransportScenario.class); private static final Logger LOG = Log.getLogger(TransportScenario.class);
@ -78,25 +82,34 @@ public class TransportScenario
protected String servletPath = "/servlet"; protected String servletPath = "/servlet";
protected HttpClient client; protected HttpClient client;
protected Path sockFile; protected Path sockFile;
protected final BlockingQueue<String> requestLog= new BlockingArrayQueue<>(); protected final BlockingQueue<String> requestLog = new BlockingArrayQueue<>();
public TransportScenario(final Transport transport) throws IOException public TransportScenario(final Transport transport) throws IOException
{ {
this.transport = transport; this.transport = transport;
if(sockFile == null || !Files.exists( sockFile )) Path unixSocketTmp;
String tmpProp = System.getProperty("unix.socket.tmp");
if (StringUtil.isBlank(tmpProp))
unixSocketTmp = MavenTestingUtils.getTargetPath();
else
unixSocketTmp = Paths.get(tmpProp);
sockFile = Files.createTempFile(unixSocketTmp, "unix", ".sock");
if (sockFile.toAbsolutePath().toString().length() > UnixSocketConnector.MAX_UNIX_SOCKET_PATH_LENGTH)
{ {
Path target = MavenTestingUtils.getTargetPath(); Files.delete(sockFile);
sockFile = Files.createTempFile(target,"unix", ".sock" ); Path tmp = Paths.get("/tmp");
Files.delete( sockFile ); assumeTrue(Files.exists(tmp) && Files.isDirectory(tmp));
sockFile = Files.createTempFile(tmp, "unix", ".sock");
} }
Files.delete(sockFile);
} }
public Optional<String> getNetworkConnectorLocalPort() public Optional<String> getNetworkConnectorLocalPort()
{ {
if (connector instanceof ServerConnector) if (connector instanceof ServerConnector)
{ {
ServerConnector serverConnector = (ServerConnector) connector; ServerConnector serverConnector = (ServerConnector)connector;
return Optional.of(Integer.toString(serverConnector.getLocalPort())); return Optional.of(Integer.toString(serverConnector.getLocalPort()));
} }
@ -107,7 +120,7 @@ public class TransportScenario
{ {
if (connector instanceof ServerConnector) if (connector instanceof ServerConnector)
{ {
ServerConnector serverConnector = (ServerConnector) connector; ServerConnector serverConnector = (ServerConnector)connector;
return Optional.of(serverConnector.getLocalPort()); return Optional.of(serverConnector.getLocalPort());
} }
@ -132,12 +145,12 @@ public class TransportScenario
return new HttpClient(transport); return new HttpClient(transport);
} }
public Connector newServerConnector(Server server) throws Exception public Connector newServerConnector(Server server)
{ {
if (transport == Transport.UNIX_SOCKET) if (transport == Transport.UNIX_SOCKET)
{ {
UnixSocketConnector unixSocketConnector = new UnixSocketConnector(server, provideServerConnectionFactory( transport )); UnixSocketConnector unixSocketConnector = new UnixSocketConnector(server, provideServerConnectionFactory(transport));
unixSocketConnector.setUnixSocket( sockFile.toString() ); unixSocketConnector.setUnixSocket(sockFile.toString());
return unixSocketConnector; return unixSocketConnector;
} }
return new ServerConnector(server, provideServerConnectionFactory(transport)); return new ServerConnector(server, provideServerConnectionFactory(transport));
@ -149,10 +162,7 @@ public class TransportScenario
ret.append(getScheme()); ret.append(getScheme());
ret.append("://localhost"); ret.append("://localhost");
Optional<String> localPort = getNetworkConnectorLocalPort(); Optional<String> localPort = getNetworkConnectorLocalPort();
if (localPort.isPresent()) localPort.ifPresent(s -> ret.append(':').append(s));
{
ret.append(':').append(localPort.get());
}
return ret.toString(); return ret.toString();
} }
@ -235,13 +245,13 @@ public class TransportScenario
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
} }
return result.toArray(new ConnectionFactory[result.size()]); return result.toArray(new ConnectionFactory[0]);
} }
public void setConnectionIdleTimeout(long idleTimeout) public void setConnectionIdleTimeout(long idleTimeout)
{ {
if (connector instanceof AbstractConnector) if (connector instanceof AbstractConnector)
AbstractConnector.class.cast(connector).setIdleTimeout(idleTimeout); ((AbstractConnector)connector).setIdleTimeout(idleTimeout);
} }
public void setServerIdleTimeout(long idleTimeout) public void setServerIdleTimeout(long idleTimeout)
@ -252,9 +262,10 @@ public class TransportScenario
else else
setConnectionIdleTimeout(idleTimeout); setConnectionIdleTimeout(idleTimeout);
} }
public void start(Handler handler) throws Exception public void start(Handler handler) throws Exception
{ {
start(handler,null); start(handler, null);
} }
public void start(Handler handler, Consumer<HttpClient> config) throws Exception public void start(Handler handler, Consumer<HttpClient> config) throws Exception
@ -284,7 +295,7 @@ public class TransportScenario
client.setExecutor(clientThreads); client.setExecutor(clientThreads);
client.setSocketAddressResolver(new SocketAddressResolver.Sync()); client.setSocketAddressResolver(new SocketAddressResolver.Sync());
if (config!=null) if (config != null)
config.accept(client); config.accept(client);
client.start(); client.start();
@ -317,7 +328,7 @@ public class TransportScenario
server.setRequestLog((request, response) -> server.setRequestLog((request, response) ->
{ {
int status = response.getCommittedMetaData().getStatus(); int status = response.getCommittedMetaData().getStatus();
requestLog.offer(String.format("%s %s %s %03d",request.getMethod(),request.getRequestURI(),request.getProtocol(),status)); requestLog.offer(String.format("%s %s %s %03d", request.getMethod(), request.getRequestURI(), request.getProtocol(), status));
}); });
server.setHandler(handler); server.setHandler(handler);
@ -326,7 +337,7 @@ public class TransportScenario
{ {
server.start(); server.start();
} }
catch ( Exception e ) catch (Exception e)
{ {
e.printStackTrace(); e.printStackTrace();
} }
@ -375,25 +386,25 @@ public class TransportScenario
{ {
stopClient(); stopClient();
} }
catch (Exception ignore) catch (Exception x)
{ {
LOG.ignore(ignore); LOG.ignore(x);
} }
try try
{ {
stopServer(); stopServer();
} }
catch (Exception ignore) catch (Exception x)
{ {
LOG.ignore(ignore); LOG.ignore(x);
} }
if (sockFile!=null) if (sockFile != null)
{ {
try try
{ {
Files.deleteIfExists( sockFile ); Files.deleteIfExists(sockFile);
} }
catch (IOException e) catch (IOException e)
{ {
@ -401,6 +412,4 @@ public class TransportScenario
} }
} }
} }
} }

View File

@ -208,10 +208,10 @@ public class AnnotationTest extends HttpServlet
boolean fragInitParamResult = "123".equals(config.getInitParameter("extra1")) && "345".equals(config.getInitParameter("extra2")); boolean fragInitParamResult = "123".equals(config.getInitParameter("extra1")) && "345".equals(config.getInitParameter("extra2"));
out.println("<p><b>Result: "+(fragInitParamResult? "<span class=\"pass\">PASS": "<span class=\"fail\">FAIL")+"</span></p>"); out.println("<p><b>Result: "+(fragInitParamResult? "<span class=\"pass\">PASS": "<span class=\"fail\">FAIL")+"</span></p>");
__HandlesTypes = Arrays.asList( "javax.servlet.GenericServlet", __HandlesTypes = Arrays.asList( "javax.servlet.GenericServlet",
"javax.servlet.http.HttpServlet", "javax.servlet.http.HttpServlet",
"com.acme.test.AsyncListenerServlet", "com.acme.test.AsyncListenerServlet",
"com.acme.test.ClassLoaderServlet",
"com.acme.test.AnnotationTest", "com.acme.test.AnnotationTest",
"com.acme.test.RoleAnnotationTest", "com.acme.test.RoleAnnotationTest",
"com.acme.test.MultiPartTest", "com.acme.test.MultiPartTest",