This is an automated email from the ASF dual-hosted git repository. remm pushed a commit to branch 9.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/9.0.x by this push: new 0d27655 Add support for Unix domain sockets for NIO 0d27655 is described below commit 0d276556881446aa499fefd496d567606bc0ddec Author: remm <r...@apache.org> AuthorDate: Wed Dec 23 11:45:58 2020 +0100 Add support for Unix domain sockets for NIO This requires Java 16 or later, and NIO (NIO2 did not get the feature). This does not remove the socket on shutdown, not sure what the best behavior is there. The socket is closed and Java doesn't do anything about that. This uses a bit of reflection, maybe the unixDomainSocketPath attribute can be added to avoid that, if the feature is actually popular. When using the feature, the JMX and thread names are slightly adjusted, and using the port attribute is optional. Based on a PR submitted by Graham Leggett https://github.com/apache/tomcat/pull/382 --- java/org/apache/catalina/connector/Connector.java | 56 ++++++----- java/org/apache/coyote/AbstractProtocol.java | 31 +++--- .../org/apache/tomcat/util/compat/Jre16Compat.java | 15 +++ java/org/apache/tomcat/util/compat/JreCompat.java | 9 ++ .../apache/tomcat/util/net/AbstractEndpoint.java | 2 +- java/org/apache/tomcat/util/net/NioEndpoint.java | 107 +++++++++++++++++++-- webapps/docs/changelog.xml | 7 ++ webapps/docs/config/http.xml | 21 ++++ 8 files changed, 205 insertions(+), 43 deletions(-) diff --git a/java/org/apache/catalina/connector/Connector.java b/java/org/apache/catalina/connector/Connector.java index 1cc1580..28e0019 100644 --- a/java/org/apache/catalina/connector/Connector.java +++ b/java/org/apache/catalina/connector/Connector.java @@ -944,23 +944,30 @@ public class Connector extends LifecycleMBeanBase { StringBuilder sb = new StringBuilder("type="); sb.append(type); - sb.append(",port="); - int port = getPortWithOffset(); - if (port > 0) { - sb.append(port); + Object path = getProperty("unixDomainSocketPath"); + if (path != null) { + // Maintain MBean name compatibility, even if not accurate + sb.append(",port=0,address="); + sb.append(ObjectName.quote(path.toString())); } else { - sb.append("auto-"); - sb.append(getProperty("nameIndex")); - } - String address = ""; - if (addressObj instanceof InetAddress) { - address = ((InetAddress) addressObj).getHostAddress(); - } else if (addressObj != null) { - address = addressObj.toString(); - } - if (address.length() > 0) { - sb.append(",address="); - sb.append(ObjectName.quote(address)); + sb.append(",port="); + int port = getPortWithOffset(); + if (port > 0) { + sb.append(port); + } else { + sb.append("auto-"); + sb.append(getProperty("nameIndex")); + } + String address = ""; + if (addressObj instanceof InetAddress) { + address = ((InetAddress) addressObj).getHostAddress(); + } else if (addressObj != null) { + address = addressObj.toString(); + } + if (address.length() > 0) { + sb.append(",address="); + sb.append(ObjectName.quote(address)); + } } return sb.toString(); } @@ -1053,7 +1060,7 @@ public class Connector extends LifecycleMBeanBase { protected void startInternal() throws LifecycleException { // Validate settings before starting - if (getPortWithOffset() < 0) { + if (getProperty("unixDomainSocketPath") == null && getPortWithOffset() < 0) { throw new LifecycleException(sm.getString( "coyoteConnector.invalidPort", Integer.valueOf(getPortWithOffset()))); } @@ -1119,12 +1126,17 @@ public class Connector extends LifecycleMBeanBase { StringBuilder sb = new StringBuilder("Connector["); sb.append(getProtocol()); sb.append('-'); - int port = getPortWithOffset(); - if (port > 0) { - sb.append(port); + Object path = getProperty("unixDomainSocketPath"); + if (path != null) { + sb.append(path.toString()); } else { - sb.append("auto-"); - sb.append(getProperty("nameIndex")); + int port = getPortWithOffset(); + if (port > 0) { + sb.append(port); + } else { + sb.append("auto-"); + sb.append(getProperty("nameIndex")); + } } sb.append(']'); return sb.toString(); diff --git a/java/org/apache/coyote/AbstractProtocol.java b/java/org/apache/coyote/AbstractProtocol.java index 79b4a87..39a4f8a 100644 --- a/java/org/apache/coyote/AbstractProtocol.java +++ b/java/org/apache/coyote/AbstractProtocol.java @@ -370,22 +370,27 @@ public abstract class AbstractProtocol<S> implements ProtocolHandler, private String getNameInternal() { StringBuilder name = new StringBuilder(getNamePrefix()); name.append('-'); - if (getAddress() != null) { - name.append(getAddress().getHostAddress()); - name.append('-'); - } - int port = getPortWithOffset(); - if (port == 0) { - // Auto binding is in use. Check if port is known - name.append("auto-"); - name.append(getNameIndex()); - port = getLocalPort(); - if (port != -1) { + String path = getProperty("unixDomainSocketPath"); + if (path != null) { + name.append(path); + } else { + if (getAddress() != null) { + name.append(getAddress().getHostAddress()); name.append('-'); + } + int port = getPortWithOffset(); + if (port == 0) { + // Auto binding is in use. Check if port is known + name.append("auto-"); + name.append(getNameIndex()); + port = getLocalPort(); + if (port != -1) { + name.append('-'); + name.append(port); + } + } else { name.append(port); } - } else { - name.append(port); } return name.toString(); } diff --git a/java/org/apache/tomcat/util/compat/Jre16Compat.java b/java/org/apache/tomcat/util/compat/Jre16Compat.java index 406824f..c142417 100644 --- a/java/org/apache/tomcat/util/compat/Jre16Compat.java +++ b/java/org/apache/tomcat/util/compat/Jre16Compat.java @@ -22,6 +22,7 @@ import java.net.ProtocolFamily; import java.net.SocketAddress; import java.net.StandardProtocolFamily; import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; @@ -35,15 +36,18 @@ class Jre16Compat extends Jre9Compat { private static final Class<?> unixDomainSocketAddressClazz; private static final Method openServerSocketChannelFamilyMethod; private static final Method unixDomainSocketAddressOfMethod; + private static final Method openSocketChannelFamilyMethod; static { Class<?> c1 = null; Method m1 = null; Method m2 = null; + Method m3 = null; try { c1 = Class.forName("java.net.UnixDomainSocketAddress"); m1 = ServerSocketChannel.class.getMethod("open", ProtocolFamily.class); m2 = c1.getMethod("of", String.class); + m3 = SocketChannel.class.getMethod("open", ProtocolFamily.class); } catch (ClassNotFoundException e) { if (c1 == null) { // Must be pre-Java 16 @@ -56,6 +60,7 @@ class Jre16Compat extends Jre9Compat { unixDomainSocketAddressClazz = c1; openServerSocketChannelFamilyMethod = m1; unixDomainSocketAddressOfMethod = m2; + openSocketChannelFamilyMethod = m3; } static boolean isSupported() { @@ -82,4 +87,14 @@ class Jre16Compat extends Jre9Compat { } } + @Override + public SocketChannel openUnixDomainSocketChannel() { + try { + return (SocketChannel) openSocketChannelFamilyMethod.invoke + (null, StandardProtocolFamily.valueOf("UNIX")); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new UnsupportedOperationException(e); + } + } + } diff --git a/java/org/apache/tomcat/util/compat/JreCompat.java b/java/org/apache/tomcat/util/compat/JreCompat.java index db72a56..9b991ba 100644 --- a/java/org/apache/tomcat/util/compat/JreCompat.java +++ b/java/org/apache/tomcat/util/compat/JreCompat.java @@ -25,6 +25,7 @@ import java.net.SocketAddress; import java.net.URL; import java.net.URLConnection; import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; import java.util.Deque; import java.util.jar.JarFile; @@ -311,4 +312,12 @@ public class JreCompat { throw new UnsupportedOperationException(sm.getString("jreCompat.noUnixDomainSocket")); } + /** + * Create socket channel using the specified socket domain socket address. + * @return the socket channel + */ + public SocketChannel openUnixDomainSocketChannel() { + throw new UnsupportedOperationException(sm.getString("jreCompat.noUnixDomainSocket")); + } + } diff --git a/java/org/apache/tomcat/util/net/AbstractEndpoint.java b/java/org/apache/tomcat/util/net/AbstractEndpoint.java index ef4618b..471190d 100644 --- a/java/org/apache/tomcat/util/net/AbstractEndpoint.java +++ b/java/org/apache/tomcat/util/net/AbstractEndpoint.java @@ -971,7 +971,7 @@ public abstract class AbstractEndpoint<S,U> { /** * Unlock the server socket acceptor threads using bogus connections. */ - private void unlockAccept() { + protected void unlockAccept() { // Only try to unlock the acceptor if it is necessary if (acceptor == null || acceptor.getState() != AcceptorState.RUNNING) { return; diff --git a/java/org/apache/tomcat/util/net/NioEndpoint.java b/java/org/apache/tomcat/util/net/NioEndpoint.java index 35e2a93..0573938 100644 --- a/java/org/apache/tomcat/util/net/NioEndpoint.java +++ b/java/org/apache/tomcat/util/net/NioEndpoint.java @@ -22,6 +22,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.net.SocketTimeoutException; import java.nio.ByteBuffer; import java.nio.channels.CancelledKeyException; @@ -35,8 +36,15 @@ import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.channels.WritableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; import java.util.ConcurrentModificationException; import java.util.Iterator; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; @@ -50,7 +58,9 @@ import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.IntrospectionUtils; import org.apache.tomcat.util.collections.SynchronizedQueue; import org.apache.tomcat.util.collections.SynchronizedStack; +import org.apache.tomcat.util.compat.JreCompat; import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState; +import org.apache.tomcat.util.net.Acceptor.AcceptorState; import org.apache.tomcat.util.net.jsse.JSSESupport; /** @@ -132,6 +142,27 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> public void setUseInheritedChannel(boolean useInheritedChannel) { this.useInheritedChannel = useInheritedChannel; } public boolean getUseInheritedChannel() { return useInheritedChannel; } + + /** + * Path for the Unix domain socket, used to create the socket address. + */ + private String unixDomainSocketPath = null; + public String getUnixDomainSocketPath() { return this.unixDomainSocketPath; } + public void setUnixDomainSocketPath(String unixDomainSocketPath) { + this.unixDomainSocketPath = unixDomainSocketPath; + } + + + /** + * Permissions which will be set on the Unix domain socket if it is created. + */ + private String unixDomainSocketPathPermissions = null; + public String getUnixDomainSocketPathPermissions() { return this.unixDomainSocketPathPermissions; } + public void setUnixDomainSocketPathPermissions(String unixDomainSocketPathPermissions) { + this.unixDomainSocketPathPermissions = unixDomainSocketPathPermissions; + } + + /** * Priority of the poller thread. */ @@ -220,12 +251,7 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> // Separated out to make it easier for folks that extend NioEndpoint to // implement custom [server]sockets protected void initServerSocket() throws Exception { - if (!getUseInheritedChannel()) { - serverSock = ServerSocketChannel.open(); - socketProperties.setProperties(serverSock.socket()); - InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset()); - serverSock.socket().bind(addr,getAcceptCount()); - } else { + if (getUseInheritedChannel()) { // Retrieve the channel provided by the OS Channel ic = System.inheritedChannel(); if (ic instanceof ServerSocketChannel) { @@ -234,6 +260,28 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> if (serverSock == null) { throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited")); } + } else if (getUnixDomainSocketPath() != null) { + SocketAddress sa = JreCompat.getInstance().getUnixDomainSocketAddress(getUnixDomainSocketPath()); + serverSock = JreCompat.getInstance().openUnixDomainServerSocketChannel(); + serverSock.bind(sa, getAcceptCount()); + if (getUnixDomainSocketPathPermissions() != null) { + FileAttribute<Set<PosixFilePermission>> attrs = + PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString(getUnixDomainSocketPathPermissions())); + Path path = Paths.get(getUnixDomainSocketPath()); + if (attrs != null) { + Files.setAttribute(path, attrs.name(), attrs.value()); + } else { + java.io.File file = path.toFile(); + file.setReadable(true, false); + file.setWritable(true, false); + file.setExecutable(false, false); + } + } + } else { + serverSock = ServerSocketChannel.open(); + socketProperties.setProperties(serverSock.socket()); + InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset()); + serverSock.bind(addr, getAcceptCount()); } serverSock.configureBlocking(true); //mimic APR behavior } @@ -361,6 +409,40 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> // ------------------------------------------------------ Protected Methods + + @Override + protected void unlockAccept() { + if (getUnixDomainSocketPath() == null) { + super.unlockAccept(); + } else { + // Only try to unlock the acceptor if it is necessary + if (acceptor == null || acceptor.getState() != AcceptorState.RUNNING) { + return; + } + try { + SocketAddress sa = JreCompat.getInstance().getUnixDomainSocketAddress(getUnixDomainSocketPath()); + try (SocketChannel socket = JreCompat.getInstance().openUnixDomainSocketChannel()) { + // With a UDS, expect no delay connecting and no defer accept + socket.connect(sa); + } + // Wait for upto 1000ms acceptor threads to unlock + long waitLeft = 1000; + while (waitLeft > 0 && + acceptor.getState() == AcceptorState.RUNNING) { + Thread.sleep(5); + waitLeft -= 5; + } + } catch(Throwable t) { + ExceptionUtils.handleThrowable(t); + if (getLog().isDebugEnabled()) { + getLog().debug(sm.getString( + "endpoint.debug.unlock.fail", String.valueOf(getPortWithOffset())), t); + } + } + } + } + + protected NioSelectorPool getSelectorPool() { return selectorPool; } @@ -421,7 +503,9 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> // Set socket properties // Disable blocking, polling will be used socket.configureBlocking(false); - socketProperties.setProperties(socket.socket()); + if (getUnixDomainSocketPath() == null) { + socketProperties.setProperties(socket.socket()); + } socketWrapper.setReadTimeout(getConnectionTimeout()); socketWrapper.setWriteTimeout(getConnectionTimeout()); @@ -1024,6 +1108,15 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> public NioSocketWrapper(NioChannel channel, NioEndpoint endpoint) { super(channel, endpoint); + if (endpoint.getUnixDomainSocketPath() != null) { + // Pretend localhost for easy compatibility + localAddr = "127.0.0.1"; + localName = "localhost"; + localPort = 0; + remoteAddr = "127.0.0.1"; + remoteHost = "localhost"; + remotePort = 0; + } pool = endpoint.getSelectorPool(); nioChannels = endpoint.getNioChannels(); poller = endpoint.getPoller(); diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 7e2c761..da0a0d0 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -151,6 +151,13 @@ combination with the Servlet non-blocking IO API. It was possible that some requests could get dropped. (markt) </fix> + <add> + Add support for using Unix domain sockets for NIO when running + on Java 16 or later. This uses NIO specific + <code>unixDomainSocketPath</code> and + <code>unixDomainSocketPathPermissions</code> attributes. + Based on a PR submitted by Graham Leggett. (remm) + </add> </changelog> </subsection> <subsection name="Jasper"> diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml index 2a03bbe..5c539a9 100644 --- a/webapps/docs/config/http.xml +++ b/webapps/docs/config/http.xml @@ -922,6 +922,27 @@ property if present.</p> </attribute> + <attribute name="unixDomainSocketPath" required="false"> + <p>Where supported, the path to a Unix Domain Socket that this + <strong>Connector</strong> will create and await incoming connections. + Tomcat will NOT automatically remove the socket on server shutdown. + If the socket already exists, care must be taken by the administrator + to remove the socket after verifying that the socket isn't already + being used by an existing Tomcat process. Using this requires + Java 16 or later. When this is specified, the otherwise mandatory + <code>port</code> attribute may be omitted.</p> + </attribute> + + <attribute name="unixDomainSocketPathPermissions" required="false"> + <p>Where supported, the posix permissions that will be applied to the + to the Unix Domain Socket specified with + <code>unixDomainSocketPath</code> above. The + permissions are specified as a string of nine characters, in three sets + of three: (r)ead, (w)rite and e(x)ecute for owner, group and others + respectively. If a permission is not granted, a hyphen is used. If + unspecified, the permissions default to <code>rw-rw-rw-</code>.</p> + </attribute> + <attribute name="useInheritedChannel" required="false"> <p>(bool)Defines if this connector should inherit an inetd/systemd network socket. Only one connector can inherit a network socket. This can option can be --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org