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

Reply via email to