This is an automated email from the ASF dual-hosted git repository.

michaelo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/master by this push:
     new a616bf3  Add support for Unix Domain Sockets (APR Protocol)
a616bf3 is described below

commit a616bf385a350175a33a0ebf09d8b6688344e9e3
Author: minfrin <minf...@users.noreply.github.com>
AuthorDate: Fri Jan 15 17:12:51 2021 +0000

    Add support for Unix Domain Sockets (APR Protocol)
    
    Add support and test case for Unix Domain Sockets in
    org.apache.coyote.http11.Http11AprProtocol, to support
    users using Java versions older than 16.
    
    Requires tomcat-native 1.2.26 and above.
---
 .../apache/catalina/core/AprLifecycleListener.java |   3 +-
 .../apache/catalina/core/LocalStrings.properties   |   2 +-
 .../catalina/core/LocalStrings_es.properties       |   2 +-
 .../catalina/core/LocalStrings_fr.properties       |   2 +-
 .../catalina/core/LocalStrings_ja.properties       |   2 +-
 .../catalina/core/LocalStrings_ko.properties       |   2 +-
 .../catalina/core/LocalStrings_zh_CN.properties    |   2 +-
 java/org/apache/tomcat/util/net/AprEndpoint.java   | 118 +++++++---
 .../apache/tomcat/util/net/LocalStrings.properties |   1 +
 .../tomcat/jni/TestUnixDomainSocketServer.java     | 240 +++++++++++++++++++++
 webapps/docs/changelog.xml                         |   5 +
 webapps/docs/config/http.xml                       |  43 +++-
 12 files changed, 382 insertions(+), 40 deletions(-)

diff --git a/java/org/apache/catalina/core/AprLifecycleListener.java 
b/java/org/apache/catalina/core/AprLifecycleListener.java
index 9321cf8..6b6a8e7 100644
--- a/java/org/apache/catalina/core/AprLifecycleListener.java
+++ b/java/org/apache/catalina/core/AprLifecycleListener.java
@@ -247,7 +247,8 @@ public class AprLifecycleListener
                 Boolean.valueOf(Library.APR_HAVE_IPV6),
                 Boolean.valueOf(Library.APR_HAS_SENDFILE),
                 Boolean.valueOf(Library.APR_HAS_SO_ACCEPTFILTER),
-                Boolean.valueOf(Library.APR_HAS_RANDOM)));
+                Boolean.valueOf(Library.APR_HAS_RANDOM),
+                Boolean.valueOf(Library.APR_HAVE_UNIX)));
 
         AprStatus.setAprAvailable(true);
     }
diff --git a/java/org/apache/catalina/core/LocalStrings.properties 
b/java/org/apache/catalina/core/LocalStrings.properties
index f276b29..f1ede08 100644
--- a/java/org/apache/catalina/core/LocalStrings.properties
+++ b/java/org/apache/catalina/core/LocalStrings.properties
@@ -74,7 +74,7 @@ aprListener.aprInitDebug=The Apache Tomcat Native library 
could not be found usi
 aprListener.aprInitError=The Apache Tomcat Native library failed to load. The 
error reported was [{0}]
 aprListener.currentFIPSMode=Current FIPS mode: [{0}]
 aprListener.enterAlreadyInFIPSMode=AprLifecycleListener is configured to force 
entering FIPS mode, but library is already in FIPS mode [{0}]
-aprListener.flags=APR capabilities: IPv6 [{0}], sendfile [{1}], accept filters 
[{2}], random [{3}].
+aprListener.flags=APR capabilities: IPv6 [{0}], sendfile [{1}], accept filters 
[{2}], random [{3}], UDS [{4}].
 aprListener.initializeFIPSFailed=Failed to enter FIPS mode
 aprListener.initializeFIPSSuccess=Successfully entered FIPS mode
 aprListener.initializedOpenSSL=OpenSSL successfully initialized [{0}]
diff --git a/java/org/apache/catalina/core/LocalStrings_es.properties 
b/java/org/apache/catalina/core/LocalStrings_es.properties
index c6604d7..89d247d 100644
--- a/java/org/apache/catalina/core/LocalStrings_es.properties
+++ b/java/org/apache/catalina/core/LocalStrings_es.properties
@@ -55,7 +55,7 @@ applicationServletRegistration.setServletSecurity.ise=No se 
pueden añadir restr
 
 aprListener.aprDestroy=No pude apagar la biblioteca nativa de Apache Tomcat
 aprListener.aprInit=La biblioteca nativa de Apache Tomcat basada en ARP que 
permite un rendimiento óptimo en entornos de desarrollo no ha sido hallada en 
java.library.path: [{0}]
-aprListener.flags=Capacidades APR: IPv6 [{0}], enviar fichero [{1}], aceptar 
filtros [{2}], aleatorio [{3}].
+aprListener.flags=Capacidades APR: IPv6 [{0}], enviar fichero [{1}], aceptar 
filtros [{2}], aleatorio [{3}], UDS [{4}].
 aprListener.initializedOpenSSL=OpenSSL inicializado correctamente [{0}]
 aprListener.initializingFIPS=Inicializando modo FIPS...
 aprListener.sslInit=No pude inicializar el SSLEngine (Motor SSL)
diff --git a/java/org/apache/catalina/core/LocalStrings_fr.properties 
b/java/org/apache/catalina/core/LocalStrings_fr.properties
index 0a20e7e..20d0ba0 100644
--- a/java/org/apache/catalina/core/LocalStrings_fr.properties
+++ b/java/org/apache/catalina/core/LocalStrings_fr.properties
@@ -74,7 +74,7 @@ aprListener.aprInitDebug=La librairie Apache Tomcat Native 
basée sur APR n''a p
 aprListener.aprInitError=La librairie Apache Tomcat Native basée sur APR n''a 
pas pu être chargée, l''erreur retournée est [{0}]
 aprListener.currentFIPSMode=Mode FIPS actuel : [{0}]
 aprListener.enterAlreadyInFIPSMode=AprLifecycleListener est configuré pour 
forcer le mode FIPS mais la librairie est déjà en mode FIPS [{0}]
-aprListener.flags=Fonctionnalités d''APR : IPv6 [{0}], sendfile [{1}], accept 
filters [{2}], random [{3}]
+aprListener.flags=Fonctionnalités d''APR : IPv6 [{0}], sendfile [{1}], accept 
filters [{2}], random [{3}, UDS [{4}]]
 aprListener.initializeFIPSFailed=Echec d'entrée en mode FIPS
 aprListener.initializeFIPSSuccess=Entrée avec succès en mode FIPS
 aprListener.initializedOpenSSL=OpenSSL a été initialisé avec succès [{0}]
diff --git a/java/org/apache/catalina/core/LocalStrings_ja.properties 
b/java/org/apache/catalina/core/LocalStrings_ja.properties
index 86e6831..5a98640 100644
--- a/java/org/apache/catalina/core/LocalStrings_ja.properties
+++ b/java/org/apache/catalina/core/LocalStrings_ja.properties
@@ -74,7 +74,7 @@ aprListener.aprInitDebug=APRベースのApache Tomcatネイティブライブラ
 aprListener.aprInitError=APRベースのApache Tomcatネイティブライブラリをロードできませんでした。 
報告されたエラーは[{0}]でした
 aprListener.currentFIPSMode=現在のFIPSモード:[{0}]
 
aprListener.enterAlreadyInFIPSMode=AprLifecycleListenerは強制的にFIPSモードに入るように設定されていますが、ライブラリはすでにFIPSモードになっています[{0}]
-aprListener.flags=APR機能:IPv6 [{0}]、sendfile {1}]、受け入れフィルタ[{2}]、ランダム[{3}]
+aprListener.flags=APR機能:IPv6 [{0}]、sendfile {1}]、受け入れフィルタ[{2}]、ランダム[{3}]、UDS 
[{4}]
 aprListener.initializeFIPSFailed=FIPS モードに変更できません。
 aprListener.initializeFIPSSuccess=FIPS モードに入りました。
 aprListener.initializedOpenSSL=OpenSSLは[{0}]を正常に初期化しました。
diff --git a/java/org/apache/catalina/core/LocalStrings_ko.properties 
b/java/org/apache/catalina/core/LocalStrings_ko.properties
index b96fddc..a4165b4 100644
--- a/java/org/apache/catalina/core/LocalStrings_ko.properties
+++ b/java/org/apache/catalina/core/LocalStrings_ko.properties
@@ -74,7 +74,7 @@ aprListener.aprInitDebug=[{0}](이)라는 이름들을 사용하여, java.librar
 aprListener.aprInitError=APR 기반 Apache Tomcat Native 라이브러리를 로드하지 못했습니다. 보고된 
오류는 [{0}]입니다.
 aprListener.currentFIPSMode=현재의 FIPS 모드: [{0}]
 aprListener.enterAlreadyInFIPSMode=AprLifecycleListener가 강제로 FIPS 모드로 들어가도록 
설정되었으나, 라이브러리가 이미 FIPS 모드 [{0}]에 있습니다.
-aprListener.flags=APR 용량정보들: IPv6 [{0}], sendfile [{1}], accept filters [{2}], 
random [{3}].
+aprListener.flags=APR 용량정보들: IPv6 [{0}], sendfile [{1}], accept filters [{2}], 
random [{3}], UDS [{4}].
 aprListener.initializeFIPSFailed=FIPS 모드로 진입하지 못했습니다.
 aprListener.initializeFIPSSuccess=FIPS 모드로 성공적으로 진입했습니다.
 aprListener.initializedOpenSSL=OpenSSL이 성공적으로 초기화되었습니다: [{0}]
diff --git a/java/org/apache/catalina/core/LocalStrings_zh_CN.properties 
b/java/org/apache/catalina/core/LocalStrings_zh_CN.properties
index acc70eb..dfd43d0 100644
--- a/java/org/apache/catalina/core/LocalStrings_zh_CN.properties
+++ b/java/org/apache/catalina/core/LocalStrings_zh_CN.properties
@@ -75,7 +75,7 @@ aprListener.aprInitDebug=在java.library.path[{1}]上使用名称[{0}]找不到
 aprListener.aprInitError=基于APR的本地库加载失败.错误报告为[{0}]
 aprListener.currentFIPSMode=当前FIPS模式:[{0}]。
 aprListener.enterAlreadyInFIPSMode=AprLifecycleListener 
配置为强制进入FIPS模式,但库已处于FIPS模式[{0}]
-aprListener.flags=APR功能:IPv6[{0}]、sendfile[{1}]、accept 
filters[{2}]、random[{3}]。
+aprListener.flags=APR功能:IPv6[{0}]、sendfile[{1}]、accept 
filters[{2}]、random[{3}]、UDS [{4}]。
 aprListener.initializeFIPSFailed=进入FIPS模式失败
 aprListener.initializeFIPSSuccess=成功的进入FIPS 模式
 aprListener.initializedOpenSSL=OpenSSL成功初始化 [{0}]
diff --git a/java/org/apache/tomcat/util/net/AprEndpoint.java 
b/java/org/apache/tomcat/util/net/AprEndpoint.java
index 489006b..37b1e81 100644
--- a/java/org/apache/tomcat/util/net/AprEndpoint.java
+++ b/java/org/apache/tomcat/util/net/AprEndpoint.java
@@ -23,6 +23,11 @@ import java.net.SocketTimeoutException;
 import java.nio.ByteBuffer;
 import java.nio.channels.CompletionHandler;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+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.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
@@ -248,6 +253,26 @@ public class AprEndpoint extends 
AbstractEndpoint<Long,Long> implements SNICallB
     }
 
 
+     /**
+      * 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;
+     }
+
+
     // --------------------------------------------------------- Public Methods
 
     /**
@@ -287,6 +312,9 @@ public class AprEndpoint extends 
AbstractEndpoint<Long,Long> implements SNICallB
     @Override
     public void bind() throws Exception {
 
+        int family;
+        String hostname = null;
+
         // Create the root APR memory pool
         try {
             rootPool = Pool.create(0);
@@ -296,52 +324,88 @@ public class AprEndpoint extends 
AbstractEndpoint<Long,Long> implements SNICallB
 
         // Create the pool for the server socket
         serverSockPool = Pool.create(rootPool);
+
         // Create the APR address that will be bound
-        String addressStr = null;
-        if (getAddress() != null) {
-            addressStr = getAddress().getHostAddress();
-        }
-        int family = Socket.APR_INET;
-        if (Library.APR_HAVE_IPV6) {
-            if (addressStr == null) {
-                if (!OS.IS_BSD) {
+        if (getUnixDomainSocketPath() != null) {
+            if (Library.APR_HAVE_UNIX) {
+                hostname = getUnixDomainSocketPath();
+                family = Socket.APR_UNIX;
+            }
+            else {
+                throw new 
Exception(sm.getString("endpoint.init.unixnotavail"));
+            }
+        }
+        else {
+
+            if (getAddress() != null) {
+                hostname = getAddress().getHostAddress();
+            }
+            family = Socket.APR_INET;
+            if (Library.APR_HAVE_IPV6) {
+                if (hostname == null) {
+                    if (!OS.IS_BSD) {
+                        family = Socket.APR_UNSPEC;
+                    }
+                } else if (hostname.indexOf(':') >= 0) {
                     family = Socket.APR_UNSPEC;
                 }
-            } else if (addressStr.indexOf(':') >= 0) {
-                family = Socket.APR_UNSPEC;
             }
-         }
+        }
+
+        long sockAddress = Address.info(hostname, family, getPortWithOffset(), 
0, rootPool);
 
-        long inetAddress = Address.info(addressStr, family, 
getPortWithOffset(), 0, rootPool);
         // Create the APR server socket
-        serverSock = Socket.create(Address.getInfo(inetAddress).family,
+        if (family == Socket.APR_UNIX) {
+            serverSock = Socket.create(family, Socket.SOCK_STREAM, 0, 
rootPool);
+        }
+        else {
+            serverSock = Socket.create(Address.getInfo(sockAddress).family,
                 Socket.SOCK_STREAM,
                 Socket.APR_PROTO_TCP, rootPool);
-        if (OS.IS_UNIX) {
-            Socket.optSet(serverSock, Socket.APR_SO_REUSEADDR, 1);
-        }
-        if (Library.APR_HAVE_IPV6) {
-            if (getIpv6v6only()) {
-                Socket.optSet(serverSock, Socket.APR_IPV6_V6ONLY, 1);
-            } else {
-                Socket.optSet(serverSock, Socket.APR_IPV6_V6ONLY, 0);
+            if (OS.IS_UNIX) {
+                Socket.optSet(serverSock, Socket.APR_SO_REUSEADDR, 1);
             }
+            if (Library.APR_HAVE_IPV6) {
+                if (getIpv6v6only()) {
+                    Socket.optSet(serverSock, Socket.APR_IPV6_V6ONLY, 1);
+                } else {
+                    Socket.optSet(serverSock, Socket.APR_IPV6_V6ONLY, 0);
+                }
+            }
+            // Deal with the firewalls that tend to drop the inactive sockets
+            Socket.optSet(serverSock, Socket.APR_SO_KEEPALIVE, 1);
         }
-        // Deal with the firewalls that tend to drop the inactive sockets
-        Socket.optSet(serverSock, Socket.APR_SO_KEEPALIVE, 1);
+
         // Bind the server socket
-        int ret = Socket.bind(serverSock, inetAddress);
+        int ret = Socket.bind(serverSock, sockAddress);
         if (ret != 0) {
             throw new Exception(sm.getString("endpoint.init.bind", "" + ret, 
Error.strerror(ret)));
         }
+
         // Start listening on the server socket
         ret = Socket.listen(serverSock, getAcceptCount());
         if (ret != 0) {
             throw new Exception(sm.getString("endpoint.init.listen", "" + ret, 
Error.strerror(ret)));
         }
-        if (OS.IS_WIN32 || OS.IS_WIN64) {
-            // On Windows set the reuseaddr flag after the bind/listen
-            Socket.optSet(serverSock, Socket.APR_SO_REUSEADDR, 1);
+
+        if (family == Socket.APR_UNIX) {
+            if (getUnixDomainSocketPathPermissions() != null) {
+                FileAttribute<Set<PosixFilePermission>> attrs =
+                         
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString(
+                                 getUnixDomainSocketPathPermissions()));
+                Files.setAttribute(Paths.get(getUnixDomainSocketPath()), 
attrs.name(), attrs.value());
+            }
+            else {
+                java.io.File file = 
Paths.get(getUnixDomainSocketPath()).toFile();
+                file.setReadable(true, false);
+                file.setWritable(true, false);
+                file.setExecutable(false, false);
+            }
+        } else {
+            if (OS.IS_WIN32 || OS.IS_WIN64) {
+                // On Windows set the reuseaddr flag after the bind/listen
+                Socket.optSet(serverSock, Socket.APR_SO_REUSEADDR, 1);
+            }
         }
 
         // Enable Sendfile by default if it has not been configured but usage 
on
diff --git a/java/org/apache/tomcat/util/net/LocalStrings.properties 
b/java/org/apache/tomcat/util/net/LocalStrings.properties
index bcf697c..54dd3d9 100644
--- a/java/org/apache/tomcat/util/net/LocalStrings.properties
+++ b/java/org/apache/tomcat/util/net/LocalStrings.properties
@@ -88,6 +88,7 @@ endpoint.init.bind=Socket bind failed: [{0}] [{1}]
 endpoint.init.bind.inherited=No inherited channel while the connector was 
configured to use one
 endpoint.init.listen=Socket listen failed: [{0}] [{1}]
 endpoint.init.notavail=APR not available
+endpoint.init.unixnotavail=Unix Domain Socket support not available
 endpoint.invalidJmxNameSslHost=Unable to generate a valid JMX object name for 
the SSLHostConfig associated with host [{0}]
 endpoint.invalidJmxNameSslHostCert=Unable to generate a valid JMX object name 
for the SSLHostConfigCertificate associated with host [{0}] and certificate 
type [{1}]
 endpoint.jmxRegistrationFailed=Failed to register the JMX object with name 
[{0}]
diff --git a/test/org/apache/tomcat/jni/TestUnixDomainSocketServer.java 
b/test/org/apache/tomcat/jni/TestUnixDomainSocketServer.java
new file mode 100644
index 0000000..27722f9
--- /dev/null
+++ b/test/org/apache/tomcat/jni/TestUnixDomainSocketServer.java
@@ -0,0 +1,240 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.tomcat.jni;
+
+import java.util.concurrent.CountDownLatch;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+
+/*
+ * Tests for Unix Domain Sockets.
+ *
+ * While System.nanotime() is available and may have a resolution of ~100 ns on
+ * some platforms, those same platforms do not use as precise a timer for 
socket
+ * timeouts. Therefore, a much larger error margin (100 ms) is used.
+ *
+ * It is known that this larger error margin is required for Windows 10. It may
+ * be worth revisiting the choice of error margin once that platform is no
+ * longer supported.
+ *
+ * @deprecated  The scope of the APR/Native Library will be reduced in Tomcat
+ *              10.1.x onwards to only those components required to provide
+ *              OpenSSL integration with the NIO and NIO2 connectors.
+ */
+@Deprecated
+public class TestUnixDomainSocketServer extends AbstractJniTest {
+
+    private static final String PATH = System.getProperty("java.io.tmpdir") + 
System.getProperty("file.separator") + "tomcat.sock";
+    // 100 ms == 100 000 000 ns
+    private static final long ERROR_MARGIN = 100000000;
+
+    private long pool = 0;
+    private long serverSocket = 0;
+    private long clientSocket = 0;
+
+    @Before
+    public void init() throws Exception {
+        Assume.assumeTrue(Library.APR_HAVE_UNIX);
+        pool = Pool.create(0);
+        long unixAddress = Address.info(PATH, Socket.APR_UNIX,
+                                        0, 0, pool);
+        serverSocket = Socket.create(Socket.APR_UNIX, Socket.SOCK_STREAM,
+                                     0, pool);
+        int rc = Socket.bind(serverSocket, unixAddress);
+        Assert.assertEquals("Can't bind: " + Error.strerror(rc), 0, rc);
+        Socket.listen(serverSocket, 5);
+
+    }
+
+
+    @After
+    public void destroy() {
+        if (clientSocket != 0) {
+            Socket.close(clientSocket);
+            Socket.destroy(clientSocket);
+        }
+        if (serverSocket != 0) {
+            Socket.close(serverSocket);
+            Socket.destroy(serverSocket);
+        }
+        if (pool != 0) {
+            Pool.destroy(pool);
+        }
+    }
+
+
+    @Test
+    public void testBlockingReadFromClientWithTimeout() throws Exception {
+        /* Start the client that connects to the server */
+        Client client = new Client(PATH);
+        client.start();
+
+        /* Accept the client connection */
+        clientSocket = Socket.accept(serverSocket);
+
+        /* Configure a 1s timeout for reading from client */
+        Socket.timeoutSet(clientSocket, 1000000);
+        long timeout = Socket.timeoutGet(clientSocket);
+        Assert.assertEquals("Socket.timeoutGet clientSocket failed", 1000000, 
timeout);
+
+        byte [] buf = new byte[1];
+        long start = System.nanoTime();
+        while (Socket.recv(clientSocket, buf, 0, 1) == 1) {
+        }
+        long wait = System.nanoTime() - start;
+        Assert.assertFalse("Socket.timeoutSet failed (<1s) [" + wait + "] +-[" 
+ ERROR_MARGIN + "]",
+                wait < 1000000000 - ERROR_MARGIN);
+        Assert.assertFalse("Socket.timeoutSet failed (>2s) [" + wait + "] +-[" 
+ ERROR_MARGIN + "]",
+                wait > 2000000000 + ERROR_MARGIN);
+
+        client.countDown();
+        client.join();
+    }
+
+
+    @Test
+    public void testNonBlockingReadFromClient() throws Exception {
+        /* Start the client that connects to the server */
+        Client client = new Client(PATH);
+        client.start();
+
+        /* Accept the client connection */
+        clientSocket = Socket.accept(serverSocket);
+
+        /* Configure the connection for non-blocking */
+        Socket.optSet(clientSocket, Socket.APR_SO_NONBLOCK, 1);
+        int val = Socket.optGet(clientSocket, Socket.APR_SO_NONBLOCK);
+        Assert.assertEquals("Socket.optGet clientSocket failed", 1, val);
+
+        byte [] buf = new byte[1];
+        long start = System.nanoTime();
+        while (Socket.recv(clientSocket, buf, 0, 1) == 1) {
+        }
+        long wait = System.nanoTime() - start;
+        Assert.assertFalse("non_blocking client Socket.APR_SO_NONBLOCK failed 
(>2ms) [" + wait +
+                "] +-[" + ERROR_MARGIN + "]", wait > 2000000 + ERROR_MARGIN);
+
+        client.countDown();
+        client.join();
+    }
+
+
+    @Test
+    public void testNonBlockingReadThenBlockingReadFromClient() throws 
Exception {
+        /* Start the client that connects to the server */
+        Client client = new Client(PATH);
+        client.start();
+
+        /* Accept the client connection */
+        clientSocket = Socket.accept(serverSocket);
+
+        /* Configure the connection for non-blocking */
+        Socket.optSet(clientSocket, Socket.APR_SO_NONBLOCK, 1);
+
+        byte [] buf = new byte[1];
+        long start = System.nanoTime();
+        while (Socket.recv(clientSocket, buf, 0, 1) == 1) {
+        }
+        long wait = System.nanoTime() - start;
+        Assert.assertFalse("non_blocking client Socket.APR_SO_NONBLOCK failed 
(>1ms) [" + wait +
+                "] +-[" + ERROR_MARGIN + "]", wait > 1000000 + ERROR_MARGIN);
+
+        /* Configure for blocking */
+        Socket.optSet(clientSocket, Socket.APR_SO_NONBLOCK, 0);
+        Socket.timeoutSet(clientSocket, 2000);
+        start = System.nanoTime();
+        while (Socket.recv(clientSocket, buf, 0, 1) == 1) {
+        }
+        wait = System.nanoTime() - start;
+        Assert.assertFalse("non_blocking client Socket.APR_SO_NONBLOCK false 
failed (<1ms) [" +
+                wait + "] +-[" + ERROR_MARGIN + "]", wait < 1000000 - 
ERROR_MARGIN);
+
+        client.countDown();
+        client.join();
+    }
+
+
+    @Test
+    public void testNonBlockingAcceptWithNoClient() throws Exception {
+        Socket.optSet(serverSocket, Socket.APR_SO_NONBLOCK, 1);
+        int val = Socket.optGet(serverSocket, Socket.APR_SO_NONBLOCK);
+        Assert.assertEquals("Socket.optGet serverSocket failed", 1, val);
+
+        long start = System.nanoTime();
+        boolean ok = false;
+        try {
+            Socket.accept(serverSocket);
+        } catch (Exception ex) {
+            ok = true;
+        }
+        long wait = System.nanoTime() - start;
+        Assert.assertTrue("Timeout failed", ok);
+        Assert.assertFalse("non_blocking accept Socket.APR_SO_NONBLOCK failed 
(>10ms) [" + wait +
+                "] +-[" + ERROR_MARGIN + "]", wait > 10000000 + ERROR_MARGIN);
+    }
+
+
+    /**
+     * Simple client that connects, sends a single byte then closes the
+     * connection.
+     */
+    private static class Client extends java.lang.Thread {
+
+        private final String path;
+        private final CountDownLatch complete = new CountDownLatch(1);
+
+        public Client(String path) throws Exception {
+            this.path = path;
+        }
+
+        public void countDown() {
+            complete.countDown();
+        }
+
+        @Override
+        public void run() {
+
+            long clientPool = Pool.create(0);
+            try {
+
+                long unixAddress = Address.info(path, Socket.APR_UNIX,
+                                                0, 0, clientPool);
+                long clientSocket = Socket.create(Socket.APR_UNIX, 
Socket.SOCK_STREAM,
+                                                  0, clientPool);
+                int rc = Socket.connect(clientSocket, unixAddress);
+                Assert.assertEquals("Can't connect: " + Error.strerror(rc), 0, 
rc);
+
+                byte[] buf = new byte[1];
+                buf[0] = 'A';
+
+                Socket.send(clientSocket, buf, 0, 1);
+                complete.await();
+                Socket.close(clientSocket);
+
+            } catch (Exception ex) {
+                ex.printStackTrace();
+            } finally {
+                Pool.destroy(clientPool);
+            }
+
+        }
+    }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 86123e9..ee05083 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -314,6 +314,11 @@
         specified to wait for client connections to complete and close before
         the Container hierarchy is stopped. (markt)
       </add>
+      <add>
+        <bug>64943</bug>: Add support for Unix Domain Sockets to
+        <code>org.apache.coyote.http11.Http11AprProtocol</code>. Depends on
+        <code>tomcat-native</code> 1.2.26 and up. (minfrin)
+      </add>
       <fix>
         <bug>64921</bug>: Ensure that the 
<code>LoadBalancerDrainingValve</code>
         uses the correct setting for the secure attribute for any session
diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml
index 4f2d17b..fdb86cd 100644
--- a/webapps/docs/config/http.xml
+++ b/webapps/docs/config/http.xml
@@ -898,12 +898,8 @@
       <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>
+        When this is specified, the otherwise mandatory <code>port</code>
+        attribute may be omitted.</p>
       </attribute>
 
       <attribute name="unixDomainSocketPathPermissions" required="false">
@@ -1158,6 +1154,41 @@
   </subsection>
 
 
+  <subsection name="Unix Domain Socket Support">
+
+  <p>When the <code>unixDomainSocketPath</code> attribute is used, connectors
+  that support Unix Domain Sockets will bind to the socket at the given path.
+  </p>
+
+  <p>For users of Java 16 and higher, support is provided within the NIO
+  connectors. For users of Java earlier than 16, support is provided by
+  the <code>org.apache.coyote.http11.Http11AprProtocol</code> connector when
+  used with the Apache Tomcat Native library v1.2.26 and up, along with
+  Apache Portable Runtime v1.6 and higher.
+  </p>
+
+  <p>The socket path is created with read and write permissions for all
+  users. To protect this socket, place it in a directory with suitable
+  permissions appropriately configured to restrict access as required.
+  Alternatively, on platforms that support posix permissions, the
+  permissions on the socket can be set directly with the
+  <code>unixDomainSocketPathPermissions</code> option.
+  </p>
+
+  <p>Tomcat will automatically remove the socket on server shutdown. If the
+  socket already exists startup will fail. 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.</p>
+
+  <p>The Unix Domain Socket can be accessed using the
+  <code>--unix-socket</code> option of the <code>curl</code> command line
+  client, and the Unix Domain Socket support in Apache HTTP server's
+  <code>mod_proxy</code> module.
+  </p>
+
+  </subsection>
+
+
   <subsection name="SSL Support">
 
   <p>You can enable SSL support for a particular instance of this


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to