Fixes #6043 - Reimplement UnixSocket support based on Java 16. (#6522)

Fixes #6043 - Reimplement UnixSocket support based on Java 16.

* Introduced new module "jetty-server-unixdomain".
It uses reflection to access the Java 16 Unix-Domain classes to keep compatibility with the other modules and the build.
* Added Jetty module with only HTTP/1.1 support for now (requires review of the modules to reuse them with various connectors).
* Updated documentation to mention UnixDomainServerConnector.
* Updated client libraries to support Unix-Domain.
* Updated PROXY protocol implementation to support Unix-Domain.
* Replaced unix.socket.tmp with better named jetty.unixdomain.dir property.
Defaulted jetty.unixdomain.dir property to system property user.home under Windows.
Simplified code that runs Unix-Domain tests.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2021-08-05 10:04:37 +02:00 committed by GitHub
parent 8de7d55bd3
commit 49a08450c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1303 additions and 169 deletions

2
Jenkinsfile vendored
View File

@ -113,7 +113,7 @@ def mavenBuild(jdk, cmdline, mvnName) {
"MAVEN_OPTS=-Xms2g -Xmx4g -Djava.awt.headless=true"]) {
configFileProvider(
[configFile(fileId: 'oss-settings.xml', variable: 'GLOBAL_MVN_SETTINGS')]) {
sh "mvn --no-transfer-progress -s $GLOBAL_MVN_SETTINGS -Dmaven.repo.local=.repository -Pci -V -B -e -Djetty.testtracker.log=true $cmdline -Dunix.socket.tmp=/tmp/unixsocket"
sh "mvn --no-transfer-progress -s $GLOBAL_MVN_SETTINGS -Dmaven.repo.local=.repository -Pci -V -B -e -Djetty.testtracker.log=true $cmdline"
}
}
}

View File

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

View File

@ -201,6 +201,11 @@
<artifactId>http2-http-client-transport</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-unixdomain-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-slf4j-impl</artifactId>

View File

@ -41,8 +41,8 @@ A wildcard domain name which will match only one level of arbitrary subdomains.
An IP address may be set as a virtual host to indicate that a web application should handle requests received on the network interface with that IP address for protocols that do not indicate a host name such as HTTP/0.9 or HTTP/1.0.
`@ConnectorName`::
A Jetty `ServerConnector` name to indicate that a web application should handle requests received on the `ServerConnector` with that name, and therefore received on a specific IP port.
A `ServerConnector` name can be set via link:{javadoc-url}/org/eclipse/jetty/server/AbstractConnector.html#setName(java.lang.String)[].
A Jetty server `Connector` name to indicate that a web application should handle requests received on the server `Connector` with that name, and therefore received on a specific socket address (either an IP port for `ServerConnector`, or a Unix-Domain path for `UnixDomainServerConnector`).
A server `Connector` name can be set via link:{javadoc-url}/org/eclipse/jetty/server/AbstractConnector.html#setName(java.lang.String)[].
`www.√integral.com`::
Non-ASCII and https://en.wikipedia.org/wiki/Internationalized_domain_name[IDN] domain names can be set as virtual hosts using https://en.wikipedia.org/wiki/Punycode[Puny Code] equivalents that may be obtained from a https://www.punycoder.com/[Punycode/IDN converters].
@ -134,7 +134,7 @@ To achieve this, you simply use the same context path of `/` for each of your we
[[og-deploy-virtual-hosts-port]]
===== Different Port, Different Web Application
Sometimes it is required to serve different web applications from different IP ports, and therefore from different ``ServerConnector``s.
Sometimes it is required to serve different web applications from different socket addresses (either different IP ports, or different Unix-Domain paths), and therefore from different server ``Connector``s.
For example, you want requests to `+http://localhost:8080/+` to be served by one web application, but requests to `+http://localhost:9090/+` to be served by another web application.

View File

@ -131,7 +131,7 @@ In the cases where you need to enhance Jetty with a custom functionality, you ca
For example, let's assume that you need to add a custom auditing component that integrates with the auditing tools used by your company.
This custom auditing component should measure the HTTP request processing times and record them (how they are recorded is irrelevant here -- could be in a local log file or sent via network to an external service).
The Jetty libraries already provide a way to measure HTTP request processing times via xref:{prog-guide}#pg-server-http-channel-events[`HttpChannel` events]: you write a custom component that implements the `HttpChannel.Listener` interface and add it as a bean to the `ServerConnector` that receives the HTTP requests.
The Jetty libraries already provide a way to measure HTTP request processing times via xref:{prog-guide}#pg-server-http-channel-events[`HttpChannel` events]: you write a custom component that implements the `HttpChannel.Listener` interface and add it as a bean to the server `Connector` that receives the HTTP requests.
The steps to create a Jetty module are similar to those necessary to xref:og-modules-custom-modify[modify an existing module]:

View File

@ -26,7 +26,7 @@ Each `ManagedSelector` wraps an instance of `java.nio.channels.Selector` that in
NOTE: TODO: add image
`SocketChannel` instances can be created by network clients when connecting to a server and by a network server when accepting connections from network clients.
`SocketChannel` instances can be created by clients when connecting to a server and by a server when accepting connections from clients.
In both cases the `SocketChannel` instance is passed to `SelectorManager` (which passes it to `ManagedSelector` and eventually to `java.nio.channels.Selector`) to be registered for use within Jetty.
It is possible for an application to create the `SocketChannel` instances outside Jetty, even perform some initial network traffic also outside Jetty (for example for authentication purposes), and then pass the `SocketChannel` instance to `SelectorManager` for use within Jetty.
@ -50,7 +50,7 @@ include::{doc_code}/org/eclipse/jetty/docs/programming/SelectorManagerDocs.java[
``SocketChannel``s that are passed to `SelectorManager` are wrapped into two related components: an link:{javadoc-url}/org/eclipse/jetty/io/EndPoint.html[`EndPoint`] and a link:{javadoc-url}/org/eclipse/jetty/io/Connection.html[`Connection`].
`EndPoint` is the Jetty abstraction for a `SocketChannel`: you can read bytes from an `EndPoint` via `EndPoint.fill(ByteBuffer)`, you can write bytes to an `EndPoint` via `EndPoint.flush(ByteBuffer...)` and `EndPoint.write(Callback, ByteBuffer...)`, you can close an `EndPoint` via `EndPoint.close()`, etc.
`EndPoint` is the Jetty abstraction for a `SocketChannel`: you can read bytes from an `EndPoint` via `EndPoint.fill(ByteBuffer)`, you can write bytes to an `EndPoint` via `EndPoint.flush(ByteBuffer\...)` and `EndPoint.write(Callback, ByteBuffer\...)`, you can close an `EndPoint` via `EndPoint.close()`, etc.
`Connection` is the Jetty abstraction that is responsible to read bytes from the `EndPoint` and to deserialize the read bytes into objects.
For example, an HTTP/1.1 server-side `Connection` implementation is responsible to deserialize HTTP/1.1 request bytes into an HTTP request object.
@ -63,9 +63,9 @@ The writing side for a specific protocol _may_ be implemented in the `Connection
While there is primarily just one implementation of `EndPoint`,link:{javadoc-url}/org/eclipse/jetty/io/SocketChannelEndPoint.html[`SocketChannelEndPoint`] (used both on the client-side and on the server-side), there are many implementations of `Connection`, typically two for each protocol (one for the client-side and one for the server-side).
The `EndPoint` and `Connection` pairs can be chained, for example in case of encrypted communication using the TLS protocol.
There is an `EndPoint` and `Connection` TLS pair where the `EndPoint` reads the encrypted bytes from the network and the `Connection` decrypts them; next in the chain there is an `EndPoint` and `Connection` pair where the `EndPoint` "reads" decrypted bytes (provided by the previous `Connection`) and the `Connection` deserializes them into specific protocol objects (for example HTTP/2 frame objects).
There is an `EndPoint` and `Connection` TLS pair where the `EndPoint` reads the encrypted bytes from the socket and the `Connection` decrypts them; next in the chain there is an `EndPoint` and `Connection` pair where the `EndPoint` "reads" decrypted bytes (provided by the previous `Connection`) and the `Connection` deserializes them into specific protocol objects (for example HTTP/2 frame objects).
Certain protocols, such as WebSocket, start the communication with the server using one protocol (e.g. HTTP/1.1), but then change the communication to use another protocol (e.g. WebSocket).
Certain protocols, such as WebSocket, start the communication with the server using one protocol (for example, HTTP/1.1), but then change the communication to use another protocol (for example, WebSocket).
`EndPoint` supports changing the `Connection` object on-the-fly via `EndPoint.upgrade(Connection)`.
This allows to use the HTTP/1.1 `Connection` during the initial communication and later to replace it with a WebSocket `Connection`.
@ -75,9 +75,9 @@ NOTE: TODO: add a section on `UpgradeFrom` and `UpgradeTo`?
Creating `Connection` instances is performed on the server-side by link:{javadoc-url}/org/eclipse/jetty/server/ConnectionFactory.html[`ConnectionFactory`]s and on the client-side by link:{javadoc-url}/org/eclipse/jetty/io/ClientConnectionFactory.html[`ClientConnectionFactory`]s
On the server-side, the component that aggregates a `SelectorManager` with a set of ``ConnectionFactory``s is link:{javadoc-url}/org/eclipse/jetty/server/ServerConnector.html[`ServerConnector`]s, see xref:pg-server-io-arch[].
On the server-side, the component that aggregates a `SelectorManager` with a set of ``ConnectionFactory``s is link:{javadoc-url}/org/eclipse/jetty/server/ServerConnector.html[`ServerConnector`]s for TCP/IP sockets, and link:{JDURL}/org/eclipse/jetty/unixdomain/server/UnixDomainServerConnector.html[`UnixDomainServerConnector`] for Unix-Domain sockets (see the xref:pg-server-io-arch[server-side architecture section] for more information).
On the client-side, the components that aggregates a `SelectorManager` with a set of ``ClientConnectionFactory``s are link:{javadoc-url}/org/eclipse/jetty/client/HttpClientTransport.html[`HttpClientTransport`] subclasses, see xref:pg-client-io-arch[].
On the client-side, the components that aggregates a `SelectorManager` with a set of ``ClientConnectionFactory``s are link:{javadoc-url}/org/eclipse/jetty/client/HttpClientTransport.html[`HttpClientTransport`] subclasses (see the xref:pg-client-io-arch[client-side architecture section] for more information).
[[pg-arch-io-endpoint]]
==== Jetty I/O: `EndPoint`
@ -86,7 +86,7 @@ The Jetty I/O library use Java NIO to handle I/O, so that I/O is non-blocking.
At the Java NIO level, in order to be notified when a `SocketChannel` has data to be read, the `SelectionKey.OP_READ` flag must be set.
In the Jetty I/O library, you can call `EndPoint.fillInterested(Callback)` to declare interest in the "read" (or "fill") event, and the `Callback` parameter is the object that is notified when such an event occurs.
In the Jetty I/O library, you can call `EndPoint.fillInterested(Callback)` to declare interest in the "read" (also called "fill") event, and the `Callback` parameter is the object that is notified when such an event occurs.
At the Java NIO level, a `SocketChannel` is always writable, unless it becomes TCP congested.
In order to be notified when a `SocketChannel` uncongests and it is therefore writable again, the `SelectionKey.OP_WRITE` flag must be set.

View File

@ -16,20 +16,31 @@
A `Connector` is the component that handles incoming requests from clients, and works in conjunction with `ConnectionFactory` instances.
The primary implementation is `org.eclipse.jetty.server.ServerConnector`.
`ServerConnector` uses a `java.nio.channels.ServerSocketChannel` to listen to a TCP port and to accept TCP connections.
The available implementations are:
Since `ServerConnector` wraps a `ServerSocketChannel`, it can be configured in a similar way, for example the port to listen to, the network address to bind to, etc.:
* `org.eclipse.jetty.server.ServerConnector`, for TCP/IP sockets.
* `org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector` for Unix-Domain sockets.
Both use a `java.nio.channels.ServerSocketChannel` to listen to a socket address and to accept socket connections.
Since `ServerConnector` wraps a `ServerSocketChannel`, it can be configured in a similar way, for example the IP port to listen to, the IP address to bind to, etc.:
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=configureConnector]
----
The _acceptors_ are threads (typically only one) that compete to accept TCP connections on the listening port.
Likewise, `UnixDomainServerConnector` also wraps a `ServerSocketChannel` and can be configured with the Unix-Domain path to listen to:
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=configureConnectorUnix]
----
The _acceptors_ are threads (typically only one) that compete to accept socket connections.
When a connection is accepted, `ServerConnector` wraps the accepted `SocketChannel` and passes it to the xref:pg-arch-io-selector-manager[`SelectorManager`].
Therefore, there is a little moment where the acceptor thread is not accepting new connections because it is busy wrapping the just accepted connection to pass it to the `SelectorManager`.
Connections that are ready to be accepted but are not accepted yet are queued in a bounded queue (at the OS level) whose capacity can be configured with the `ServerConnector.acceptQueueSize` parameter.
Connections that are ready to be accepted but are not accepted yet are queued in a bounded queue (at the OS level) whose capacity can be configured with the `acceptQueueSize` parameter.
If your application must withstand a very high rate of connections opened, configuring more than one acceptor thread may be beneficial: when one acceptor thread accepts one connection, another acceptor thread can take over accepting connections.
@ -42,7 +53,7 @@ In this case a single selector may be able to manage many sockets because chance
On the contrary, web messaging applications tend to send many small messages at a very high frequency so that sockets are rarely idle.
In this case a single selector may be able to manage less sockets because chances are that many of them will be active at the same time.
It is possible to configure more than one `ServerConnector`, each listening on a different port:
It is possible to configure more than one `ServerConnector` (each listening on a different port), or more than one `UnixDomainServerConnector` (each listening on a different path), or ``ServerConnector``s and ``UnixDomainServerConnector``s, for example:
[source,java,indent=0]
----
@ -52,9 +63,9 @@ include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPSer
[[pg-server-http-connector-protocol]]
===== Configuring Protocols
For each accepted TCP connection, `ServerConnector` asks a `ConnectionFactory` to create a `Connection` object that handles the network traffic on that TCP connection, parsing and generating bytes for a specific protocol (see xref:pg-arch-io[this section] for more details about `Connection` objects).
For each accepted socket connection, the server `Connector` asks a `ConnectionFactory` to create a `Connection` object that handles the traffic on that socket connection, parsing and generating bytes for a specific protocol (see xref:pg-arch-io[this section] for more details about `Connection` objects).
A `ServerConnector` can be configured with one or more ``ConnectionFactory``s.
A server `Connector` can be configured with one or more ``ConnectionFactory``s.
If no `ConnectionFactory` is specified then `HttpConnectionFactory` is implicitly configured.
[[pg-server-http-connector-protocol-http11]]
@ -87,7 +98,7 @@ By using those ports, a client had _prior knowledge_ that the server would speak
HTTP/2 was designed to be a smooth transition from HTTP/1.1 for users and as such the HTTP ports were not changed.
However the HTTP/2 protocol is, on the wire, a binary protocol, completely different from HTTP/1.1.
Therefore, with HTTP/2, clients that connect to port `80` may speak either HTTP/1.1 or HTTP/2, and the server must figure out which version of the HTTP protocol the client is speaking.
Therefore, with HTTP/2, clients that connect to port `80` (or to a specific Unix-Domain path) may speak either HTTP/1.1 or HTTP/2, and the server must figure out which version of the HTTP protocol the client is speaking.
Jetty can support both HTTP/1.1 and HTTP/2 on the same clear-text port by configuring both the HTTP/1.1 and the HTTP/2 ``ConnectionFactory``s:
@ -128,7 +139,7 @@ Note also that the default protocol set in the ALPN ``ConnectionFactory``, which
It is often the case that Jetty receives connections from a load balancer configured to distribute the load among many Jetty backend servers.
From the Jetty point of view, all the connections arrive from the load balancer, rather than the real clients, but is possible to configure the load balancer to forward the real client IP address and port to the backend Jetty server using the link:https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt[PROXY protocol].
From the Jetty point of view, all the connections arrive from the load balancer, rather than the real clients, but is possible to configure the load balancer to forward the real client IP address and IP port to the backend Jetty server using the link:https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt[PROXY protocol].
NOTE: The PROXY protocol is widely supported by load balancers such as link:http://cbonte.github.io/haproxy-dconv/2.2/configuration.html#5.2-send-proxy[HAProxy] (via its `send-proxy` directive), link:https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol[Nginx](via its `proxy_protocol on` directive) and others.
@ -144,3 +155,13 @@ Note also how the PROXY `ConnectionFactory` needs to know its _next_ protocol (i
Each `ConnectionFactory` is asked to create a `Connection` object for each accepted TCP connection; the `Connection` objects will be chained together to handle the bytes, each for its own protocol.
Therefore the `ProxyConnection` will handle the PROXY protocol bytes and `HttpConnection` will handle the HTTP/1.1 bytes producing a request object and response object that will be processed by ``Handler``s.
The load balancer may be configured to communicate with Jetty backend servers via Unix-Domain sockets (requires Java 16 or later).
For example:
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=proxyHTTPUnix]
----
Note that the only difference when using Unix-Domain sockets is instantiating `UnixDomainServerConnector` instead of `ServerConnector` and configuring the Unix-Domain path instead of the IP port.

View File

@ -12,23 +12,30 @@
//
[[pg-server-io-arch]]
=== Server Libraries I/O Architecture
=== Server I/O Architecture
The Jetty server libraries provide the basic components and APIs to implement a network server.
They build on the common xref:pg-arch-io[Jetty I/O Architecture] and provide server specific concepts.
The central I/O server-side component is `org.eclipse.jetty.server.ServerConnector`.
The Jetty server libraries provide I/O support for TCP/IP sockets (for both IPv4 and IPv6) and, when using Java 16 or later, for Unix-Domain sockets.
Support for Unix-Domain sockets is interesting when Jetty is deployed behind a proxy or a load-balancer: it is possible to configure the proxy or load balancer to communicate with Jetty via Unix-Domain sockets, rather than via the loopback network interface.
The central I/O server-side component are `org.eclipse.jetty.server.ServerConnector`, that handles the TCP/IP socket traffic, and `org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector`, that handles the Unix-Domain socket traffic.
`ServerConnector` and `UnixDomainServerConnector` are very similar, and while in the following sections `ServerConnector` is used, the same concepts apply to `UnixDomainServerConnector`, unless otherwise noted.
A `ServerConnector` manages a list of ``ConnectionFactory``s, that indicate what protocols the connector is able to speak.
[[pg-server-io-arch-connection-factory]]
==== Creating Connections with `ConnectionFactory`
Recall from the xref:pg-arch-io-connection[`Connection` section] of the Jetty I/O architecture that `Connection` instances are responsible for parsing bytes read from a TCP connection and generating bytes to write to that TCP connection.
Recall from the xref:pg-arch-io-connection[`Connection` section] of the Jetty I/O architecture that `Connection` instances are responsible for parsing bytes read from a socket and generating bytes to write to that socket.
On the server-side, a `ConnectionFactory` creates `Connection` instances that know how to parse and generate bytes for the specific protocol they support -- it can be either HTTP/1.1, or TLS, or FastCGI, or the link:https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt[PROXY protocol].
For example, this is how clear-text HTTP/1.1 is configured:
For example, this is how clear-text HTTP/1.1 is configured for TCP/IP sockets:
[source,java,indent=0]
----
@ -36,7 +43,24 @@ include::../{doc_code}/org/eclipse/jetty/docs/programming/server/ServerDocs.java
----
With this configuration, the `ServerConnector` will listen on port `8080`.
When a new TCP connection is established, `ServerConnector` delegates to the `ConnectionFactory` the creation of the `Connection` instance for that TCP connection, that is linked to the corresponding `EndPoint`:
Similarly, this is how clear-text HTTP/1.1 is configured for Unix-Domain sockets:
[source,java,indent=0]
----
include::../{doc_code}/org/eclipse/jetty/docs/programming/server/ServerDocs.java[tags=httpUnix]
----
With this configuration, the `UnixDomainServerConnector` will listen on file `/tmp/jetty.sock`.
[NOTE]
====
`ServerConnector` and `UnixDomainServerConnector` only differ by how they are configured -- for `ServerConnector` you specify the IP port it listens to, for `UnixDomainServerConnector` you specify the Unix-Domain path it listens to.
Both configure ``ConnectionFactory``s in exactly the same way.
====
When a new socket connection is established, `ServerConnector` delegates to the `ConnectionFactory` the creation of the `Connection` instance for that socket connection, that is linked to the corresponding `EndPoint`:
[plantuml]
----
@ -53,12 +77,12 @@ scale 1.5
circle network
circle application
network - SocketEndPoint
SocketEndPoint - HttpConnection
network - SocketChannelEndPoint
SocketChannelEndPoint - HttpConnection
HttpConnection - application
----
For every TCP connection there will be an `EndPoint` + `Connection` pair.
For every socket connection there will be an `EndPoint` + `Connection` pair.
[[pg-server-io-arch-connection-factory-wrapping]]
==== Wrapping a `ConnectionFactory`
@ -72,7 +96,7 @@ include::../{doc_code}/org/eclipse/jetty/docs/programming/server/ServerDocs.java
----
With this configuration, the `ServerConnector` will listen on port `8443`.
When a new TCP connection is established, the first `ConnectionFactory` configured in `ServerConnector` is invoked to create a `Connection`.
When a new socket connection is established, the first `ConnectionFactory` configured in `ServerConnector` is invoked to create a `Connection`.
In the example above, `SslConnectionFactory` creates a `SslConnection` and then asks to its wrapped `ConnectionFactory` (in the example, `HttpConnectionFactory`) to create the wrapped `Connection` (an `HttpConnection`) and will then link the two ``Connection``s together, in this way:
[plantuml]
@ -90,16 +114,16 @@ scale 1.5
circle network
circle application
network - SocketEndPoint
SocketEndPoint - SslConnection
network - SocketChannelEndPoint
SocketChannelEndPoint - SslConnection
SslConnection -- DecryptedEndPoint
DecryptedEndPoint - HttpConnection
HttpConnection - application
----
Bytes read by the `SocketEndPoint` will be interpreted as TLS bytes by the `SslConnection`, then decrypted and made available to the `DecryptedEndPoint` (a component part of `SslConnection`), which will then provide them to `HttpConnection`.
Bytes read by the `SocketChannelEndPoint` will be interpreted as TLS bytes by the `SslConnection`, then decrypted and made available to the `DecryptedEndPoint` (a component part of `SslConnection`), which will then provide them to `HttpConnection`.
The application writes bytes through the `HttpConnection` to the `DecryptedEndPoint`, which will encrypt them through the `SslConnection` and write the encrypted bytes to the `SocketEndPoint`.
The application writes bytes through the `HttpConnection` to the `DecryptedEndPoint`, which will encrypt them through the `SslConnection` and write the encrypted bytes to the `SocketChannelEndPoint`.
[[pg-server-io-arch-connection-factory-detecting]]
==== Choosing `ConnectionFactory` via Bytes Detection
@ -124,18 +148,18 @@ With this configuration, the detector will delegate to `SslConnectionFactory` to
<2> Creates the `ServerConnector` with `DetectorConnectionFactory` as the first `ConnectionFactory`, and `HttpConnectionFactory` as the next `ConnectionFactory` to invoke if the detection fails.
In the example above `ServerConnector` will listen on port 8181.
When a new TCP connection is established, `DetectorConnectionFactory` is invoked to create a `Connection`, because it is the first `ConnectionFactory` specified in the `ServerConnector` list.
When a new socket connection is established, `DetectorConnectionFactory` is invoked to create a `Connection`, because it is the first `ConnectionFactory` specified in the `ServerConnector` list.
`DetectorConnectionFactory` reads the initial bytes and asks to its detecting ``ConnectionFactory``s if they recognize the bytes.
In the example above, the detecting ``ConnectionFactory`` is `SslConnectionFactory` which will therefore detect whether the initial bytes are TLS bytes.
If one of the detecting ``ConnectionFactory``s recognizes the bytes, it creates a `Connection`; otherwise `DetectorConnectionFactory` will try the next `ConnectionFactory` after itself in the `ServerConnector` list.
In the example above, the next `ConnectionFactory` after `DetectorConnectionFactory` is `HttpConnectionFactory`.
The final result is that when new TCP connection is established, the initial bytes are examined: if they are TLS bytes, a `SslConnectionFactory` will create a `SslConnection` that wraps an `HttpConnection` as explained xref:pg-server-io-arch-connection-factory-wrapping[here], therefore supporting `https`; otherwise they are not TLS bytes and an `HttpConnection` is created, therefore supporting `http`.
The final result is that when new socket connection is established, the initial bytes are examined: if they are TLS bytes, a `SslConnectionFactory` will create a `SslConnection` that wraps an `HttpConnection` as explained xref:pg-server-io-arch-connection-factory-wrapping[here], therefore supporting `https`; otherwise they are not TLS bytes and an `HttpConnection` is created, therefore supporting `http`.
[[pg-server-io-arch-connection-factory-custom]]
==== Writing a Custom `ConnectionFactory`
This section explains how to use the Jetty server-side libraries to write a generic network server able to parse and generate any protocol based on TCP.
This section explains how to use the Jetty server-side libraries to write a generic network server able to parse and generate any protocol..
Let's suppose that we want to write a custom protocol that is based on JSON but has the same semantic as HTTP; let's call this custom protocol `JSONHTTP`, so that a request would look like this:

View File

@ -14,6 +14,7 @@
package org.eclipse.jetty.docs.programming.server;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.Map;
import java.util.concurrent.Executor;
@ -31,6 +32,7 @@ import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingCallback;
@ -57,6 +59,23 @@ public class ServerDocs
// end::http[]
}
public void httpUnix() throws Exception
{
// tag::httpUnix[]
// Create the HTTP/1.1 ConnectionFactory.
HttpConnectionFactory http = new HttpConnectionFactory();
Server server = new Server();
// Create the connector with the ConnectionFactory.
UnixDomainServerConnector connector = new UnixDomainServerConnector(server, http);
connector.setUnixDomainPath(Path.of("/tmp/jetty.sock"));
server.addConnector(connector);
server.start();
// end::httpUnix[]
}
public void tlsHttp() throws Exception
{
// tag::tlsHttp[]

View File

@ -14,6 +14,7 @@
package org.eclipse.jetty.docs.programming.server.http;
import java.io.IOException;
import java.nio.file.Path;
import java.util.EnumSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -62,6 +63,7 @@ import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
@ -163,14 +165,15 @@ public class HTTPServerDocs
int selectors = 1;
// Create a ServerConnector instance.
ServerConnector connector = new ServerConnector(server, 1, 1, new HttpConnectionFactory());
ServerConnector connector = new ServerConnector(server, acceptors, selectors, new HttpConnectionFactory());
// Configure TCP parameters.
// Configure TCP/IP parameters.
// The TCP port to listen to.
// The port to listen to.
connector.setPort(8080);
// The TCP address to bind to.
// The address to bind to.
connector.setHost("127.0.0.1");
// The TCP accept queue size.
connector.setAcceptQueueSize(128);
@ -179,6 +182,33 @@ public class HTTPServerDocs
// end::configureConnector[]
}
public void configureConnectorUnix() throws Exception
{
// tag::configureConnectorUnix[]
Server server = new Server();
// The number of acceptor threads.
int acceptors = 1;
// The number of selectors.
int selectors = 1;
// Create a ServerConnector instance.
UnixDomainServerConnector connector = new UnixDomainServerConnector(server, acceptors, selectors, new HttpConnectionFactory());
// Configure Unix-Domain parameters.
// The Unix-Domain path to listen to.
connector.setUnixDomainPath(Path.of("/tmp/jetty.sock"));
// The TCP accept queue size.
connector.setAcceptQueueSize(128);
server.addConnector(connector);
server.start();
// end::configureConnectorUnix[]
}
public void configureConnectors() throws Exception
{
// tag::configureConnectors[]
@ -248,6 +278,31 @@ public class HTTPServerDocs
// end::proxyHTTP[]
}
public void proxyHTTPUnix() throws Exception
{
// tag::proxyHTTPUnix[]
Server server = new Server();
// The HTTP configuration object.
HttpConfiguration httpConfig = new HttpConfiguration();
// Configure the HTTP support, for example:
httpConfig.setSendServerVersion(false);
// The ConnectionFactory for HTTP/1.1.
HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig);
// The ConnectionFactory for the PROXY protocol.
ProxyConnectionFactory proxy = new ProxyConnectionFactory(http11.getProtocol());
// Create the ServerConnector.
UnixDomainServerConnector connector = new UnixDomainServerConnector(server, proxy, http11);
connector.setUnixDomainPath(Path.of("/tmp/jetty.sock"));
server.addConnector(connector);
server.start();
// end::proxyHTTPUnix[]
}
public void tlsHttp11() throws Exception
{
// tag::tlsHttp11[]

View File

@ -25,7 +25,7 @@ import org.slf4j.LoggerFactory;
@ManagedObject
public abstract class AbstractHttpClientTransport extends ContainerLifeCycle implements HttpClientTransport
{
protected static final Logger LOG = LoggerFactory.getLogger(HttpClientTransport.class);
private static final Logger LOG = LoggerFactory.getLogger(HttpClientTransport.class);
private HttpClient client;
private ConnectionPool.Factory factory;

View File

@ -161,7 +161,7 @@ public class HttpClient extends ContainerLifeCycle
{
this.transport = Objects.requireNonNull(transport);
addBean(transport);
this.connector = ((AbstractHttpClientTransport)transport).getBean(ClientConnector.class);
this.connector = ((AbstractHttpClientTransport)transport).getContainedBeans(ClientConnector.class).stream().findFirst().orElseThrow();
addBean(handlers);
addBean(decoderFactories);
}
@ -553,24 +553,25 @@ public class HttpClient extends ContainerLifeCycle
return new ArrayList<>(destinations.values());
}
protected void send(final HttpRequest request, List<Response.ResponseListener> listeners)
protected void send(HttpRequest request, List<Response.ResponseListener> listeners)
{
HttpDestination destination = (HttpDestination)resolveDestination(request);
destination.send(request, listeners);
}
protected void newConnection(final HttpDestination destination, final Promise<Connection> promise)
protected void newConnection(HttpDestination destination, Promise<Connection> promise)
{
// Multiple threads may access the map, especially with DEBUG logging enabled.
Map<String, Object> context = new ConcurrentHashMap<>();
context.put(ClientConnectionFactory.CLIENT_CONTEXT_KEY, HttpClient.this);
context.put(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY, destination);
Origin.Address address = destination.getConnectAddress();
resolver.resolve(address.getHost(), address.getPort(), new Promise<>()
{
@Override
public void succeeded(List<InetSocketAddress> socketAddresses)
{
// Multiple threads may access the map, especially with DEBUG logging enabled.
Map<String, Object> context = new ConcurrentHashMap<>();
context.put(ClientConnectionFactory.CLIENT_CONTEXT_KEY, HttpClient.this);
context.put(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY, destination);
connect(socketAddresses, 0, context);
}
@ -1229,7 +1230,7 @@ public class HttpClient extends ContainerLifeCycle
@Override
public Iterator<ContentDecoder.Factory> iterator()
{
final Iterator<ContentDecoder.Factory> iterator = set.iterator();
Iterator<ContentDecoder.Factory> iterator = set.iterator();
return new Iterator<>()
{
@Override

View File

@ -41,6 +41,8 @@ import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>A {@link HttpClientTransport} that can dynamically switch among different application protocols.</p>
@ -79,6 +81,8 @@ import org.eclipse.jetty.io.EndPoint;
*/
public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTransport
{
private static final Logger LOG = LoggerFactory.getLogger(HttpClientTransportDynamic.class);
private final List<ClientConnectionFactory.Info> factoryInfos;
private final List<String> protocols;

View File

@ -29,11 +29,14 @@ import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.ProcessorUtils;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ManagedObject("The HTTP/1.1 client transport")
public class HttpClientTransportOverHTTP extends AbstractConnectorHttpClientTransport
{
public static final Origin.Protocol HTTP11 = new Origin.Protocol(List.of("http/1.1"), false);
private static final Logger LOG = LoggerFactory.getLogger(HttpClientTransportOverHTTP.class);
private final ClientConnectionFactory factory = new HttpClientConnectionFactory();
private int headerCacheSize = 1024;

View File

@ -34,10 +34,14 @@ import org.eclipse.jetty.util.ProcessorUtils;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ManagedObject("The FastCGI/1.0 client transport")
public class HttpClientTransportOverFCGI extends AbstractConnectorHttpClientTransport
{
private static final Logger LOG = LoggerFactory.getLogger(HttpClientTransportOverFCGI.class);
private final String scriptRoot;
public HttpClientTransportOverFCGI(String scriptRoot)

View File

@ -69,5 +69,11 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-unixdomain-server</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -14,6 +14,7 @@
package org.eclipse.jetty.fcgi.server.proxy;
import java.net.URI;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@ -36,6 +37,7 @@ import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.proxy.AsyncProxyServlet;
import org.eclipse.jetty.util.ProcessorUtils;
@ -62,6 +64,8 @@ import org.eclipse.jetty.util.ProcessorUtils;
* to force the FastCGI {@code HTTPS} parameter to the value {@code on}</li>
* <li>{@code fastCGI.envNames}, optional, a comma separated list of environment variable
* names read via {@link System#getenv(String)} that are forwarded as FastCGI parameters.</li>
* <li>{@code unixDomainPath}, optional, that specifies the Unix-Domain path the FastCGI
* server listens to.</li>
* </ul>
*
* @see TryFilesFilter
@ -122,11 +126,23 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent
String scriptRoot = config.getInitParameter(SCRIPT_ROOT_INIT_PARAM);
if (scriptRoot == null)
throw new IllegalArgumentException("Mandatory parameter '" + SCRIPT_ROOT_INIT_PARAM + "' not configured");
ClientConnector connector;
String unixDomainPath = config.getInitParameter("unixDomainPath");
if (unixDomainPath != null)
{
connector = ClientConnector.forUnixDomain(Path.of(unixDomainPath));
}
else
{
int selectors = Math.max(1, ProcessorUtils.availableProcessors() / 2);
String value = config.getInitParameter("selectors");
if (value != null)
selectors = Integer.parseInt(value);
return new HttpClient(new ProxyHttpClientTransportOverFCGI(selectors, scriptRoot));
connector = new ClientConnector();
connector.setSelectors(selectors);
}
return new HttpClient(new ProxyHttpClientTransportOverFCGI(connector, scriptRoot));
}
@Override
@ -261,9 +277,9 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent
private class ProxyHttpClientTransportOverFCGI extends HttpClientTransportOverFCGI
{
private ProxyHttpClientTransportOverFCGI(int selectors, String scriptRoot)
private ProxyHttpClientTransportOverFCGI(ClientConnector connector, String scriptRoot)
{
super(selectors, scriptRoot);
super(connector, scriptRoot);
}
@Override

View File

@ -14,9 +14,12 @@
package org.eclipse.jetty.fcgi.server.proxy;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@ -29,18 +32,22 @@ import org.eclipse.jetty.client.util.FutureResponseListener;
import org.eclipse.jetty.fcgi.FCGI;
import org.eclipse.jetty.fcgi.server.ServerFCGIConnectionFactory;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@ -49,19 +56,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class FastCGIProxyServletTest
{
public static Stream<Arguments> factories()
{
return Stream.of(
true, // send status 200
false // don't send status 200
).map(Arguments::of);
}
private final Map<String, String> fcgiParams = new HashMap<>();
private Server server;
private ServerConnector httpConnector;
private ServerConnector fcgiConnector;
private Connector fcgiConnector;
private ServletContextHandler context;
private HttpClient client;
private Path unixDomainPath;
public void prepare(boolean sendStatus200, HttpServlet servlet) throws Exception
{
@ -71,19 +72,32 @@ public class FastCGIProxyServletTest
httpConnector = new ServerConnector(server);
server.addConnector(httpConnector);
fcgiConnector = new ServerConnector(server, new ServerFCGIConnectionFactory(new HttpConfiguration(), sendStatus200));
ServerFCGIConnectionFactory fcgi = new ServerFCGIConnectionFactory(new HttpConfiguration(), sendStatus200);
if (unixDomainPath == null)
{
fcgiConnector = new ServerConnector(server, fcgi);
}
else
{
UnixDomainServerConnector connector = new UnixDomainServerConnector(server, fcgi);
connector.setUnixDomainPath(unixDomainPath);
fcgiConnector = connector;
}
server.addConnector(fcgiConnector);
final String contextPath = "/";
String contextPath = "/";
context = new ServletContextHandler(server, contextPath);
final String servletPath = "/script";
String servletPath = "/script";
FastCGIProxyServlet fcgiServlet = new FastCGIProxyServlet()
{
@Override
protected String rewriteTarget(HttpServletRequest request)
{
return "http://localhost:" + fcgiConnector.getLocalPort() + servletPath + request.getServletPath();
String uri = "http://localhost";
if (unixDomainPath == null)
uri += ":" + ((ServerConnector)fcgiConnector).getLocalPort();
return uri + servletPath + request.getServletPath();
}
};
ServletHolder fcgiServletHolder = new ServletHolder(fcgiServlet);
@ -91,6 +105,7 @@ public class FastCGIProxyServletTest
fcgiServletHolder.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, "/scriptRoot");
fcgiServletHolder.setInitParameter("proxyTo", "http://localhost");
fcgiServletHolder.setInitParameter(FastCGIProxyServlet.SCRIPT_PATTERN_INIT_PARAM, "(.+?\\.php)");
fcgiParams.forEach(fcgiServletHolder::setInitParameter);
context.addServlet(fcgiServletHolder, "*.php");
context.addServlet(new ServletHolder(servlet), servletPath + "/*");
@ -111,36 +126,36 @@ public class FastCGIProxyServletTest
}
@ParameterizedTest(name = "[{index}] sendStatus200={0}")
@MethodSource("factories")
@ValueSource(booleans = {true, false})
public void testGETWithSmallResponseContent(boolean sendStatus200) throws Exception
{
testGETWithResponseContent(sendStatus200, 1024, 0);
}
@ParameterizedTest(name = "[{index}] sendStatus200={0}")
@MethodSource("factories")
@ValueSource(booleans = {true, false})
public void testGETWithLargeResponseContent(boolean sendStatus200) throws Exception
{
testGETWithResponseContent(sendStatus200, 16 * 1024 * 1024, 0);
}
@ParameterizedTest(name = "[{index}] sendStatus200={0}")
@MethodSource("factories")
@ValueSource(booleans = {true, false})
public void testGETWithLargeResponseContentWithSlowClient(boolean sendStatus200) throws Exception
{
testGETWithResponseContent(sendStatus200, 16 * 1024 * 1024, 1);
}
private void testGETWithResponseContent(boolean sendStatus200, int length, final long delay) throws Exception
private void testGETWithResponseContent(boolean sendStatus200, int length, long delay) throws Exception
{
final byte[] data = new byte[length];
byte[] data = new byte[length];
new Random().nextBytes(data);
final String path = "/foo/index.php";
String path = "/foo/index.php";
prepare(sendStatus200, new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
{
assertTrue(request.getRequestURI().endsWith(path));
response.setContentLength(data.length);
@ -173,16 +188,20 @@ public class FastCGIProxyServletTest
}
@ParameterizedTest(name = "[{index}] sendStatus200={0}")
@MethodSource("factories")
@ValueSource(booleans = {true, false})
public void testURIRewrite(boolean sendStatus200) throws Exception
{
String originalPath = "/original/index.php";
String originalQuery = "foo=bar";
String remotePath = "/remote/index.php";
String pathAttribute = "_path_attribute";
String queryAttribute = "_query_attribute";
fcgiParams.put(FastCGIProxyServlet.ORIGINAL_URI_ATTRIBUTE_INIT_PARAM, pathAttribute);
fcgiParams.put(FastCGIProxyServlet.ORIGINAL_QUERY_ATTRIBUTE_INIT_PARAM, queryAttribute);
prepare(sendStatus200, new HttpServlet()
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
protected void service(HttpServletRequest request, HttpServletResponse response)
{
assertThat((String)request.getAttribute(FCGI.Headers.REQUEST_URI), Matchers.startsWith(originalPath));
assertEquals(originalQuery, request.getAttribute(FCGI.Headers.QUERY_STRING));
@ -190,11 +209,6 @@ public class FastCGIProxyServletTest
}
});
context.stop();
String pathAttribute = "_path_attribute";
String queryAttribute = "_query_attribute";
ServletHolder fcgi = context.getServletHandler().getServlet("fcgi");
fcgi.setInitParameter(FastCGIProxyServlet.ORIGINAL_URI_ATTRIBUTE_INIT_PARAM, pathAttribute);
fcgi.setInitParameter(FastCGIProxyServlet.ORIGINAL_QUERY_ATTRIBUTE_INIT_PARAM, queryAttribute);
context.insertHandler(new HandlerWrapper()
{
@Override
@ -216,4 +230,34 @@ public class FastCGIProxyServletTest
assertEquals(HttpStatus.OK_200, response.getStatus());
}
@Test
@EnabledForJreRange(min = JRE.JAVA_16)
public void testUnixDomain() throws Exception
{
int maxUnixDomainPathLength = 108;
Path path = Files.createTempFile("unix", ".sock");
if (path.normalize().toAbsolutePath().toString().length() > maxUnixDomainPathLength)
path = Files.createTempFile(Path.of("/tmp"), "unix", ".sock");
assertTrue(Files.deleteIfExists(path));
unixDomainPath = path;
fcgiParams.put("unixDomainPath", path.toString());
byte[] content = new byte[512];
new Random().nextBytes(content);
prepare(true, new HttpServlet()
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
{
response.getOutputStream().write(content);
}
});
ContentResponse response = client.newRequest("localhost", httpConnector.getLocalPort())
.path("/index.php")
.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
assertArrayEquals(content, response.getContent());
}
}

View File

@ -1,3 +1,3 @@
# Jetty Logging using jetty-slf4j-impl
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.client.LEVEL=DEBUG
#org.eclipse.jetty.fcgi.LEVEL=DEBUG

View File

@ -534,6 +534,7 @@
</plugin>
</plugins>
</build>
<dependencies>
<!-- Orbit Deps -->
<dependency>
@ -648,6 +649,12 @@
<artifactId>jetty-proxy</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-unixdomain-server</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-unixsocket-server</artifactId>

View File

@ -14,18 +14,24 @@
package org.eclipse.jetty.io;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ProtocolFamily;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.file.Path;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.JavaVersion;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@ -43,6 +49,12 @@ public class ClientConnector extends ContainerLifeCycle
public static final String CONNECTION_PROMISE_CONTEXT_KEY = CLIENT_CONNECTOR_CONTEXT_KEY + ".connectionPromise";
private static final Logger LOG = LoggerFactory.getLogger(ClientConnector.class);
public static ClientConnector forUnixDomain(Path path)
{
return new ClientConnector(SocketChannelWithAddress.Factory.forUnixDomain(path));
}
private final SocketChannelWithAddress.Factory factory;
private Executor executor;
private Scheduler scheduler;
private ByteBufferPool byteBufferPool;
@ -55,6 +67,16 @@ public class ClientConnector extends ContainerLifeCycle
private SocketAddress bindAddress;
private boolean reuseAddress = true;
public ClientConnector()
{
this((address, context) -> new SocketChannelWithAddress(SocketChannel.open(), address));
}
private ClientConnector(SocketChannelWithAddress.Factory factory)
{
this.factory = Objects.requireNonNull(factory);
}
public Executor getExecutor()
{
return executor;
@ -221,20 +243,16 @@ public class ClientConnector extends ContainerLifeCycle
context.put(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY, this);
context.putIfAbsent(REMOTE_SOCKET_ADDRESS_CONTEXT_KEY, address);
channel = SocketChannel.open();
SocketChannelWithAddress channelWithAddress = factory.newSocketChannelWithAddress(address, context);
channel = channelWithAddress.getSocketChannel();
address = channelWithAddress.getSocketAddress();
SocketAddress bindAddress = getBindAddress();
if (bindAddress != null)
{
boolean reuseAddress = getReuseAddress();
if (LOG.isDebugEnabled())
LOG.debug("Binding to {} to connect to {}{}", bindAddress, address, (reuseAddress ? " reusing address" : ""));
channel.setOption(StandardSocketOptions.SO_REUSEADDR, reuseAddress);
channel.bind(bindAddress);
}
bind(channel, bindAddress);
configure(channel);
boolean connected = true;
boolean blocking = isConnectBlocking();
boolean blocking = isConnectBlocking() && address instanceof InetSocketAddress;
if (LOG.isDebugEnabled())
LOG.debug("Connecting {} to {}", blocking ? "blocking" : "non-blocking", address);
if (blocking)
@ -288,9 +306,34 @@ public class ClientConnector extends ContainerLifeCycle
}
}
private void bind(SocketChannel channel, SocketAddress bindAddress)
{
try
{
boolean reuseAddress = getReuseAddress();
if (LOG.isDebugEnabled())
LOG.debug("Binding to {} reusing address {}", bindAddress, reuseAddress);
channel.setOption(StandardSocketOptions.SO_REUSEADDR, reuseAddress);
channel.bind(bindAddress);
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Could not bind {}", channel);
}
}
protected void configure(SocketChannel channel) throws IOException
{
channel.socket().setTcpNoDelay(true);
try
{
channel.setOption(StandardSocketOptions.TCP_NODELAY, true);
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Could not configure {}", channel);
}
}
protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey selectionKey)
@ -351,4 +394,77 @@ public class ClientConnector extends ContainerLifeCycle
connectFailed(failure, context);
}
}
/**
* <p>A pair/record holding a {@link SocketChannel} and a {@link SocketAddress} to connect to.</p>
*/
private static class SocketChannelWithAddress
{
private final SocketChannel channel;
private final SocketAddress address;
private SocketChannelWithAddress(SocketChannel channel, SocketAddress address)
{
this.channel = channel;
this.address = address;
}
private SocketChannel getSocketChannel()
{
return channel;
}
private SocketAddress getSocketAddress()
{
return address;
}
/**
* <p>A factory for {@link SocketChannelWithAddress} instances.</p>
*/
private interface Factory
{
private static Factory forUnixDomain(Path path)
{
return (address, context) ->
{
try
{
ProtocolFamily family = Enum.valueOf(StandardProtocolFamily.class, "UNIX");
SocketChannel socketChannel = (SocketChannel)SocketChannel.class.getMethod("open", ProtocolFamily.class).invoke(null, family);
Class<?> addressClass = Class.forName("java.net.UnixDomainSocketAddress");
SocketAddress socketAddress = (SocketAddress)addressClass.getMethod("of", Path.class).invoke(null, path);
return new SocketChannelWithAddress(socketChannel, socketAddress);
}
catch (Throwable x)
{
String message = "Unix-Domain SocketChannels are available starting from Java 16, your Java version is: " + JavaVersion.VERSION;
throw new UnsupportedOperationException(message, x);
}
};
}
/**
* <p>Creates a new {@link SocketChannel} to connect to a {@link SocketAddress}
* derived from the input socket address.</p>
* <p>The input socket address represents the destination socket address to
* connect to, as it is typically specified by a URI authority, for example
* {@code localhost:8080} if the URI is {@code http://localhost:8080/path}.</p>
* <p>However, the returned socket address may be different as the implementation
* may use a Unix-Domain socket address to physically connect to the virtual
* destination socket address given as input.</p>
* <p>The return type is a pair/record holding the socket channel and the
* socket address, with the socket channel not yet connected.
* The implementation of this methods must not call
* {@link SocketChannel#connect(SocketAddress)}, as this is done later,
* after configuring the socket, by the {@link ClientConnector} implementation.</p>
*
* @param address the destination socket address, typically specified in a URI
* @param context the context to create the new socket channel
* @return a new {@link SocketChannel} with an associated {@link SocketAddress} to connect to
* @throws IOException if the socket channel or the socket address cannot be created
*/
public SocketChannelWithAddress newSocketChannelWithAddress(SocketAddress address, Map<String, Object> context) throws IOException;
}
}
}

View File

@ -165,8 +165,10 @@ public class SocketChannelEndPoint extends AbstractEndPoint implements ManagedSe
{
return _channel.getLocalAddress();
}
catch (IOException x)
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Could not retrieve local socket address", x);
return null;
}
}
@ -178,8 +180,10 @@ public class SocketChannelEndPoint extends AbstractEndPoint implements ManagedSe
{
return _channel.getRemoteAddress();
}
catch (IOException e)
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Could not retrieve remote socket address", x);
return null;
}
}

View File

@ -585,30 +585,43 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
{
byte[] addr = new byte[4];
_buffer.get(addr);
InetAddress src = Inet4Address.getByAddress(addr);
InetAddress srcAddr = Inet4Address.getByAddress(addr);
_buffer.get(addr);
InetAddress dst = Inet4Address.getByAddress(addr);
int sp = _buffer.getChar();
int dp = _buffer.getChar();
local = new InetSocketAddress(dst, dp);
remote = new InetSocketAddress(src, sp);
InetAddress dstAddr = Inet4Address.getByAddress(addr);
int srcPort = _buffer.getChar();
int dstPort = _buffer.getChar();
local = new InetSocketAddress(dstAddr, dstPort);
remote = new InetSocketAddress(srcAddr, srcPort);
break;
}
case INET6:
{
byte[] addr = new byte[16];
_buffer.get(addr);
InetAddress src = Inet6Address.getByAddress(addr);
InetAddress srcAddr = Inet6Address.getByAddress(addr);
_buffer.get(addr);
InetAddress dst = Inet6Address.getByAddress(addr);
int sp = _buffer.getChar();
int dp = _buffer.getChar();
local = new InetSocketAddress(dst, dp);
remote = new InetSocketAddress(src, sp);
InetAddress dstAddr = Inet6Address.getByAddress(addr);
int srcPort = _buffer.getChar();
int dstPort = _buffer.getChar();
local = new InetSocketAddress(dstAddr, dstPort);
remote = new InetSocketAddress(srcAddr, srcPort);
break;
}
case UNIX:
{
byte[] addr = new byte[108];
_buffer.get(addr);
String src = UnixDomain.toPath(addr);
_buffer.get(addr);
String dst = UnixDomain.toPath(addr);
local = UnixDomain.newSocketAddress(dst);
remote = UnixDomain.newSocketAddress(src);
break;
}
default:
throw new IllegalStateException();
{
throw new IllegalStateException("Unsupported family " + _family);
}
}
proxyEndPoint = new ProxyEndPoint(endPoint, local, remote);
@ -706,7 +719,7 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
}
Transport transport;
switch (0xF & transportAndFamily)
switch (transportAndFamily & 0xF)
{
case 0:
transport = Transport.UNSPEC;
@ -723,7 +736,7 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
_length = _buffer.getChar();
if (!_local && (_family == Family.UNSPEC || _family == Family.UNIX || transport != Transport.STREAM))
if (!_local && (_family == Family.UNSPEC || transport != Transport.STREAM))
throw new IOException(String.format("Proxy v2 unsupported PROXY mode 0x%x,0x%x", versionAndCommand, transportAndFamily));
if (_length > getMaxProxyHeader())
@ -957,4 +970,47 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
_endPoint.write(callback, buffers);
}
}
private static class UnixDomain
{
private static final Class<?> unixDomainSocketAddress = probe();
private static Class<?> probe()
{
try
{
return ClassLoader.getPlatformClassLoader().loadClass("java.net.UnixDomainSocketAddress");
}
catch (Throwable ignored)
{
return null;
}
}
private static SocketAddress newSocketAddress(String path)
{
try
{
if (unixDomainSocketAddress != null)
return (SocketAddress)unixDomainSocketAddress.getMethod("of", String.class).invoke(null, path);
return null;
}
catch (Throwable ignored)
{
return null;
}
}
private static String toPath(byte[] bytes)
{
// Unix-Domain paths are zero-terminated.
int i = 0;
while (i < bytes.length)
{
if (bytes[i++] == 0)
break;
}
return new String(bytes, 0, i, StandardCharsets.US_ASCII).trim();
}
}
}

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>10.0.7-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-unixdomain-server</artifactId>
<name>Jetty :: Unix-Domain Sockets :: Server</name>
<description>Jetty Unix-Domain Sockets Server</description>
<properties>
<bundle-symbolic-name>${project.groupId}.unixdomain.server</bundle-symbolic-name>
<spotbugs.onlyAnalyze>org.eclipse.jetty.unixdomain.*</spotbugs.onlyAnalyze>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call name="addConnector">
<Arg>
<New id="unixDomainConnector" class="org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector">
<Arg><Ref refid="Server" /></Arg>
<Arg type="int"><Property name="jetty.unixdomain.acceptors" default="1"/></Arg>
<Arg type="int"><Property name="jetty.unixdomain.selectors" default="-1"/></Arg>
<Arg>
<Array type="org.eclipse.jetty.server.ConnectionFactory">
<Item>
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
<Arg name="config"><Ref refid="httpConfig" /></Arg>
</New>
</Item>
</Array>
</Arg>
<Set name="unixDomainPath">
<Call class="java.nio.file.Path" name="of">
<Arg>
<Property name="jetty.unixdomain.path">
<Default><SystemProperty name="java.io.tmpdir" default="/tmp" />/jetty.sock</Default>
</Property>
</Arg>
</Call>
</Set>
<Set name="acceptQueueSize" property="jetty.unixdomain.acceptQueueSize" />
<Set name="acceptedReceiveBufferSize" property="jetty.unixdomain.acceptedReceiveBufferSize" />
<Set name="acceptedSendBufferSize" property="jetty.unixdomain.acceptedSendBufferSize" />
</New>
</Arg>
</Call>
</Configure>

View File

@ -0,0 +1,36 @@
[description]
Enables support for clear-text HTTP/1.1 over Java 16 Unix-Domain server sockets.
[tag]
connector
unixdomain
[depends]
server
[lib]
lib/jetty-unixdomain-server-*.jar
[xml]
etc/jetty-unixdomain-http.xml
[ini-template]
# tag::documentation[]
## The number of acceptors (-1 picks a default value based on number of cores).
# jetty.unixdomain.acceptors=1
## The number of selectors (-1 picks a default value based on number of cores).
# jetty.unixdomain.selectors=-1
## The Unix-Domain path the ServerSocketChannel listens to.
# jetty.unixdomain.path=/tmp/jetty.sock
## The ServerSocketChannel accept queue backlog (0 picks the platform default).
# jetty.unixdomain.acceptQueueSize=0
## The SO_RCVBUF option for accepted SocketChannels (0 picks the platform default).
# jetty.unixdomain.acceptedReceiveBufferSize=0
## The SO_SNDBUF option for accepted SocketChannels (0 picks the platform default).
# jetty.unixdomain.acceptedSendBufferSize=0
# end::documentation[]

View File

@ -0,0 +1,20 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
module org.eclipse.jetty.unixdomain.server
{
exports org.eclipse.jetty.unixdomain.server;
requires transitive org.eclipse.jetty.server;
requires org.slf4j;
}

View File

@ -0,0 +1,326 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.unixdomain.server;
import java.io.Closeable;
import java.io.IOException;
import java.net.ProtocolFamily;
import java.net.SocketAddress;
import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions;
import java.nio.channels.Channel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.SelectorManager;
import org.eclipse.jetty.io.SocketChannelEndPoint;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.JavaVersion;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.thread.Scheduler;
/**
* <p>A {@link Connector} implementation for Unix-Domain server socket channels.</p>
* <p>UnixDomainServerConnector "listens" to a {@link #setUnixDomainPath(Path) Unix-Domain path}
* and behaves {@link ServerConnector} with respect to acceptors, selectors and connection
* factories.</p>
* <p>Important: the unix-domain path must be less than 108 bytes.
* This limit is set by the way Unix-Domain sockets work at the OS level.</p>
*/
@ManagedObject
public class UnixDomainServerConnector extends AbstractConnector
{
private final AtomicReference<Closeable> acceptor = new AtomicReference<>();
private final SelectorManager selectorManager;
private ServerSocketChannel serverChannel;
private Path unixDomainPath;
private boolean inheritChannel;
private int acceptQueueSize;
private int acceptedReceiveBufferSize;
private int acceptedSendBufferSize;
public UnixDomainServerConnector(Server server, ConnectionFactory... factories)
{
this(server, null, null, null, -1, -1, factories);
}
public UnixDomainServerConnector(Server server, int acceptors, int selectors, ConnectionFactory... factories)
{
this(server, null, null, null, acceptors, selectors, factories);
}
public UnixDomainServerConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, int selectors, ConnectionFactory... factories)
{
super(server, executor, scheduler, pool, acceptors, factories.length > 0 ? factories : new ConnectionFactory[]{new HttpConnectionFactory()});
selectorManager = newSelectorManager(getExecutor(), getScheduler(), selectors);
addBean(selectorManager, true);
}
protected SelectorManager newSelectorManager(Executor executor, Scheduler scheduler, int selectors)
{
return new UnixDomainSelectorManager(executor, scheduler, selectors);
}
@ManagedAttribute("The Unix-Domain path this connector listens to")
public Path getUnixDomainPath()
{
return unixDomainPath;
}
public void setUnixDomainPath(Path unixDomainPath)
{
this.unixDomainPath = unixDomainPath;
}
@ManagedAttribute("Whether this connector uses a server channel inherited from the JVM")
public boolean isInheritChannel()
{
return inheritChannel;
}
public void setInheritChannel(boolean inheritChannel)
{
this.inheritChannel = inheritChannel;
}
@ManagedAttribute("The accept queue size (backlog) for the server socket")
public int getAcceptQueueSize()
{
return acceptQueueSize;
}
public void setAcceptQueueSize(int acceptQueueSize)
{
this.acceptQueueSize = acceptQueueSize;
}
@ManagedAttribute("The SO_RCVBUF option for accepted sockets")
public int getAcceptedReceiveBufferSize()
{
return acceptedReceiveBufferSize;
}
public void setAcceptedReceiveBufferSize(int acceptedReceiveBufferSize)
{
this.acceptedReceiveBufferSize = acceptedReceiveBufferSize;
}
@ManagedAttribute("The SO_SNDBUF option for accepted sockets")
public int getAcceptedSendBufferSize()
{
return acceptedSendBufferSize;
}
public void setAcceptedSendBufferSize(int acceptedSendBufferSize)
{
this.acceptedSendBufferSize = acceptedSendBufferSize;
}
@Override
protected void doStart() throws Exception
{
getBeans(SelectorManager.SelectorManagerListener.class).forEach(selectorManager::addEventListener);
serverChannel = open();
addBean(serverChannel);
super.doStart();
}
@Override
protected void doStop() throws Exception
{
super.doStop();
removeBean(serverChannel);
close();
getBeans(SelectorManager.SelectorManagerListener.class).forEach(selectorManager::removeEventListener);
}
@Override
protected void accept(int acceptorID) throws IOException
{
ServerSocketChannel serverChannel = this.serverChannel;
if (serverChannel != null)
{
SocketChannel channel = serverChannel.accept();
accepted(channel);
}
}
private void accepted(SocketChannel channel) throws IOException
{
channel.configureBlocking(false);
configure(channel);
selectorManager.accept(channel);
}
protected void configure(SocketChannel channel) throws IOException
{
// Unix-Domain does not support TCP_NODELAY.
// Unix-Domain does not support SO_REUSEADDR.
int rcvBufSize = getAcceptedReceiveBufferSize();
if (rcvBufSize > 0)
channel.setOption(StandardSocketOptions.SO_RCVBUF, rcvBufSize);
int sndBufSize = getAcceptedSendBufferSize();
if (sndBufSize > 0)
channel.setOption(StandardSocketOptions.SO_SNDBUF, sndBufSize);
}
@Override
public Object getTransport()
{
return serverChannel;
}
private ServerSocketChannel open() throws IOException
{
ServerSocketChannel serverChannel = openServerSocketChannel();
if (getAcceptors() == 0)
{
serverChannel.configureBlocking(false);
acceptor.set(selectorManager.acceptor(serverChannel));
}
return serverChannel;
}
private void close() throws IOException
{
ServerSocketChannel serverChannel = this.serverChannel;
this.serverChannel = null;
IO.close(serverChannel);
Files.deleteIfExists(getUnixDomainPath());
}
private ServerSocketChannel openServerSocketChannel() throws IOException
{
ServerSocketChannel serverChannel = null;
if (isInheritChannel())
{
Channel channel = System.inheritedChannel();
if (channel instanceof ServerSocketChannel)
serverChannel = (ServerSocketChannel)channel;
else
LOG.warn("Unable to use System.inheritedChannel() {}. Trying a new Unix-Domain ServerSocketChannel at {}", channel, getUnixDomainPath());
}
if (serverChannel == null)
serverChannel = bindServerSocketChannel();
return serverChannel;
}
private ServerSocketChannel bindServerSocketChannel()
{
try
{
ProtocolFamily family = Enum.valueOf(StandardProtocolFamily.class, "UNIX");
Class<?> channelClass = Class.forName("java.nio.channels.ServerSocketChannel");
ServerSocketChannel serverChannel = (ServerSocketChannel)channelClass.getMethod("open", ProtocolFamily.class).invoke(null, family);
// Unix-Domain does not support SO_REUSEADDR.
Class<?> addressClass = Class.forName("java.net.UnixDomainSocketAddress");
SocketAddress socketAddress = (SocketAddress)addressClass.getMethod("of", Path.class).invoke(null, getUnixDomainPath());
serverChannel.bind(socketAddress, getAcceptQueueSize());
return serverChannel;
}
catch (Throwable x)
{
String message = "Unix-Domain SocketChannels are available starting from Java 16, your Java version is: " + JavaVersion.VERSION;
throw new UnsupportedOperationException(message, x);
}
}
@Override
public void setAccepting(boolean accepting)
{
super.setAccepting(accepting);
if (getAcceptors() == 0)
return;
if (accepting)
{
if (acceptor.get() == null)
{
Closeable acceptor = selectorManager.acceptor(serverChannel);
if (!this.acceptor.compareAndSet(null, acceptor))
IO.close(acceptor);
}
}
else
{
Closeable acceptor = this.acceptor.get();
if (acceptor != null && this.acceptor.compareAndSet(acceptor, null))
IO.close(acceptor);
}
}
@Override
public String toString()
{
return String.format("%s@%h[%s]", getClass().getSimpleName(), hashCode(), getUnixDomainPath());
}
protected class UnixDomainSelectorManager extends SelectorManager
{
public UnixDomainSelectorManager(Executor executor, Scheduler scheduler, int selectors)
{
super(executor, scheduler, selectors);
}
@Override
protected void accepted(SelectableChannel channel) throws IOException
{
UnixDomainServerConnector.this.accepted((SocketChannel)channel);
}
@Override
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey selectionKey)
{
SocketChannelEndPoint endPoint = new SocketChannelEndPoint((SocketChannel)channel, selector, selectionKey, getScheduler());
endPoint.setIdleTimeout(getIdleTimeout());
return endPoint;
}
@Override
public Connection newConnection(SelectableChannel channel, EndPoint endpoint, Object attachment)
{
return getDefaultConnectionFactory().newConnection(UnixDomainServerConnector.this, endpoint);
}
@Override
protected void endPointOpened(EndPoint endpoint)
{
super.endPointOpened(endpoint);
onEndPointOpened(endpoint);
}
@Override
protected void endPointClosed(EndPoint endpoint)
{
onEndPointClosed(endpoint);
super.endPointClosed(endpoint);
}
}
}

View File

@ -0,0 +1,272 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.unixdomain.server;
import java.net.SocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpProxy;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.ProxyConnectionFactory;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.util.component.LifeCycle;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import static org.eclipse.jetty.client.ProxyProtocolClientConnectionFactory.V1;
import static org.eclipse.jetty.client.ProxyProtocolClientConnectionFactory.V2;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@EnabledForJreRange(min = JRE.JAVA_16)
public class UnixDomainTest
{
private static final Class<?> unixDomainSocketAddressClass = probe();
private static Class<?> probe()
{
try
{
return ClassLoader.getPlatformClassLoader().loadClass("java.net.UnixDomainSocketAddress");
}
catch (Throwable x)
{
return null;
}
}
private ConnectionFactory[] factories = new ConnectionFactory[]{new HttpConnectionFactory()};
private Server server;
private Path unixDomainPath;
@BeforeEach
public void prepare()
{
Assumptions.assumeTrue(unixDomainSocketAddressClass != null);
}
private void start(Handler handler) throws Exception
{
server = new Server();
UnixDomainServerConnector connector = new UnixDomainServerConnector(server, factories);
String dir = System.getProperty("jetty.unixdomain.dir");
assertNotNull(dir);
unixDomainPath = Files.createTempFile(Path.of(dir), "unix_", ".sock");
assertTrue(unixDomainPath.toAbsolutePath().toString().length() < 108, "Unix-Domain path too long");
Files.delete(unixDomainPath);
connector.setUnixDomainPath(unixDomainPath);
server.addConnector(connector);
server.setHandler(handler);
server.start();
}
@AfterEach
public void dispose()
{
LifeCycle.stop(server);
}
@Test
public void testHTTPOverUnixDomain() throws Exception
{
String uri = "http://localhost:1234/path";
start(new AbstractHandler()
{
@Override
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
{
jettyRequest.setHandled(true);
// Verify the URI is preserved.
assertEquals(uri, request.getRequestURL().toString());
EndPoint endPoint = jettyRequest.getHttpChannel().getEndPoint();
// Verify the SocketAddresses.
SocketAddress local = endPoint.getLocalSocketAddress();
assertThat(local, Matchers.instanceOf(unixDomainSocketAddressClass));
SocketAddress remote = endPoint.getRemoteSocketAddress();
assertThat(remote, Matchers.instanceOf(unixDomainSocketAddressClass));
// Verify that other address methods don't throw.
local = assertDoesNotThrow(endPoint::getLocalAddress);
assertNull(local);
remote = assertDoesNotThrow(endPoint::getRemoteAddress);
assertNull(remote);
assertDoesNotThrow(endPoint::toString);
}
});
ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath);
HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector));
httpClient.start();
try
{
ContentResponse response = httpClient.newRequest(uri)
.timeout(5, TimeUnit.SECONDS)
.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
}
finally
{
httpClient.stop();
}
}
@Test
public void testHTTPOverUnixDomainWithHTTPProxy() throws Exception
{
int fakeProxyPort = 4567;
int fakeServerPort = 5678;
start(new AbstractHandler()
{
@Override
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
{
jettyRequest.setHandled(true);
// Proxied requests must have an absolute URI.
HttpURI uri = jettyRequest.getMetaData().getURI();
assertNotNull(uri.getScheme());
assertEquals(fakeServerPort, uri.getPort());
}
});
ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath);
HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector));
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", fakeProxyPort));
httpClient.start();
try
{
ContentResponse response = httpClient.newRequest("localhost", fakeServerPort)
.timeout(5, TimeUnit.SECONDS)
.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
}
finally
{
httpClient.stop();
}
}
@Test
public void testHTTPOverUnixDomainWithProxyProtocol() throws Exception
{
String srcAddr = "/proxySrcAddr";
String dstAddr = "/proxyDstAddr";
factories = new ConnectionFactory[]{new ProxyConnectionFactory(), new HttpConnectionFactory()};
start(new AbstractHandler()
{
@Override
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
{
jettyRequest.setHandled(true);
EndPoint endPoint = jettyRequest.getHttpChannel().getEndPoint();
assertThat(endPoint, Matchers.instanceOf(ProxyConnectionFactory.ProxyEndPoint.class));
assertThat(endPoint.getLocalSocketAddress(), Matchers.instanceOf(unixDomainSocketAddressClass));
assertThat(endPoint.getRemoteSocketAddress(), Matchers.instanceOf(unixDomainSocketAddressClass));
if ("/v1".equals(target))
{
// As PROXYv1 does not support UNIX, the wrapped EndPoint data is used.
Path localPath = toUnixDomainPath(endPoint.getLocalSocketAddress());
assertThat(localPath, Matchers.equalTo(unixDomainPath));
}
else if ("/v2".equals(target))
{
assertThat(toUnixDomainPath(endPoint.getLocalSocketAddress()).toString(), Matchers.equalTo(FS.separators(dstAddr)));
assertThat(toUnixDomainPath(endPoint.getRemoteSocketAddress()).toString(), Matchers.equalTo(FS.separators(srcAddr)));
}
else
{
Assertions.fail("Invalid PROXY protocol version " + target);
}
}
});
// Java 11+ portable way to implement SocketChannelWithAddress.Factory.
ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath);
HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector));
httpClient.start();
try
{
// Try PROXYv1 with the PROXY information retrieved from the EndPoint.
// PROXYv1 does not support the UNIX family.
ContentResponse response1 = httpClient.newRequest("localhost", 0)
.path("/v1")
.tag(new V1.Tag())
.timeout(5, TimeUnit.SECONDS)
.send();
assertEquals(HttpStatus.OK_200, response1.getStatus());
// Try PROXYv2 with explicit PROXY information.
var tag = new V2.Tag(V2.Tag.Command.PROXY, V2.Tag.Family.UNIX, V2.Tag.Protocol.STREAM, srcAddr, 0, dstAddr, 0, null);
ContentResponse response2 = httpClient.newRequest("localhost", 0)
.path("/v2")
.tag(tag)
.timeout(5, TimeUnit.SECONDS)
.send();
assertEquals(HttpStatus.OK_200, response2.getStatus());
}
finally
{
httpClient.stop();
}
}
private static Path toUnixDomainPath(SocketAddress address)
{
try
{
Assertions.assertNotNull(unixDomainSocketAddressClass);
return (Path)unixDomainSocketAddressClass.getMethod("getPath").invoke(address);
}
catch (Throwable x)
{
Assertions.fail(x);
throw new AssertionError();
}
}
}

View File

@ -0,0 +1,2 @@
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.unixdomain.LEVEL=DEBUG

View File

@ -18,7 +18,6 @@ import java.io.InputStream;
import java.net.ConnectException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import javax.servlet.http.HttpServletRequest;
@ -32,7 +31,6 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.unixsocket.client.HttpClientTransportOverUnixSockets;
import org.eclipse.jetty.unixsocket.server.UnixSocketConnector;
import org.eclipse.jetty.util.StringUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
@ -45,9 +43,9 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.condition.OS.LINUX;
import static org.junit.jupiter.api.condition.OS.MAC;
@ -65,18 +63,11 @@ public class UnixSocketTest
{
server = null;
httpClient = null;
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");
if (sockFile.toAbsolutePath().toString().length() > UnixSocketConnector.MAX_UNIX_SOCKET_PATH_LENGTH)
{
Path tmp = Paths.get("/tmp");
assumeTrue(Files.exists(tmp) && Files.isDirectory(tmp));
sockFile = Files.createTempFile(tmp, "unix", ".sock");
}
assertTrue(Files.deleteIfExists(sockFile), "temp sock file cannot be deleted");
String dir = System.getProperty("jetty.unixdomain.dir");
assertNotNull(dir);
sockFile = Files.createTempFile(Path.of(dir), "unix_", ".sock");
assertTrue(sockFile.toAbsolutePath().toString().length() < UnixSocketConnector.MAX_UNIX_SOCKET_PATH_LENGTH, "Unix-Domain path too long");
Files.delete(sockFile);
}
@AfterEach

16
pom.xml
View File

@ -12,6 +12,7 @@
<inceptionYear>1995</inceptionYear>
<properties>
<jetty.unixdomain.dir>/tmp</jetty.unixdomain.dir>
<compiler.source>11</compiler.source>
<compiler.target>11</compiler.target>
<compiler.release>11</compiler.release>
@ -44,7 +45,6 @@
<jboss.logging.version>3.4.2.Final</jboss.logging.version>
<jetty.perf-helper.version>1.0.6</jetty.perf-helper.version>
<ant.version>1.10.9</ant.version>
<unix.socket.tmp></unix.socket.tmp>
<!-- OSGI import-package -->
<osgi.slf4j.import.packages>org.slf4j;version="[1.7,3.0)", org.slf4j.event;version="[1.7,3.0)", org.slf4j.helpers;version="[1.7,3.0)", org.slf4j.spi;version="[1.7,3.0)"</osgi.slf4j.import.packages>
@ -150,6 +150,7 @@
<module>jetty-bom</module>
<module>documentation</module>
<module>jetty-keystore</module>
<module>jetty-unixdomain-server</module>
</modules>
<build>
@ -684,7 +685,7 @@
<runOrder>alphabetical</runOrder>
<systemPropertyVariables>
<java.io.tmpdir>${project.build.directory}</java.io.tmpdir>
<unix.socket.tmp>${unix.socket.tmp}</unix.socket.tmp>
<jetty.unixdomain.dir>${jetty.unixdomain.dir}</jetty.unixdomain.dir>
<junit.jupiter.extensions.autodetection.enabled>true</junit.jupiter.extensions.autodetection.enabled>
<jetty.testtracker.log>${jetty.testtracker.log}</jetty.testtracker.log>
</systemPropertyVariables>
@ -1243,6 +1244,17 @@
</dependencyManagement>
<profiles>
<profile>
<id>unix-domain-windows</id>
<activation>
<os>
<family>Windows</family>
</os>
</activation>
<properties>
<jetty.unixdomain.dir>${user.home}</jetty.unixdomain.dir>
</properties>
</profile>
<profile>
<id>errorprone</id>
<build>

View File

@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http2.client.HTTP2Client;
@ -38,7 +39,6 @@ import org.eclipse.jetty.unixsocket.client.HttpClientTransportOverUnixSockets;
import org.eclipse.jetty.unixsocket.server.UnixSocketConnector;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
@ -48,6 +48,7 @@ import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.params.ParameterizedTest;
@ -61,8 +62,8 @@ import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
public class DistributionTests extends AbstractJettyHomeTest
{
@ -277,20 +278,11 @@ public class DistributionTests extends AbstractJettyHomeTest
@DisabledOnOs(OS.WINDOWS) // jnr not supported on windows
public void testUnixSocket() throws Exception
{
Path tmpSockFile;
String unixSocketTmp = System.getProperty("unix.socket.tmp");
if (StringUtil.isNotBlank(unixSocketTmp))
tmpSockFile = Files.createTempFile(Paths.get(unixSocketTmp), "unix", ".sock");
else
tmpSockFile = Files.createTempFile("unix", ".sock");
if (tmpSockFile.toAbsolutePath().toString().length() > UnixSocketConnector.MAX_UNIX_SOCKET_PATH_LENGTH)
{
Path tmp = Paths.get("/tmp");
assumeTrue(Files.exists(tmp) && Files.isDirectory(tmp));
tmpSockFile = Files.createTempFile(tmp, "unix", ".sock");
}
Path sockFile = tmpSockFile;
assertTrue(Files.deleteIfExists(sockFile), "temp sock file cannot be deleted");
String dir = System.getProperty("jetty.unixdomain.dir");
assertNotNull(dir);
Path sockFile = Files.createTempFile(Path.of(dir), "unix_", ".sock");
assertTrue(sockFile.toAbsolutePath().toString().length() < UnixSocketConnector.MAX_UNIX_SOCKET_PATH_LENGTH, "Unix-Domain path too long");
Files.delete(sockFile);
String jettyVersion = System.getProperty("jettyVersion");
JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
@ -311,7 +303,7 @@ public class DistributionTests extends AbstractJettyHomeTest
File war = distribution.resolveArtifact("org.eclipse.jetty.demos:demo-jsp-webapp:war:" + jettyVersion);
distribution.installWarFile(war, "test");
try (JettyHomeTester.Run run2 = distribution.start("jetty.unixsocket.path=" + sockFile.toString()))
try (JettyHomeTester.Run run2 = distribution.start("jetty.unixsocket.path=" + sockFile))
{
assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS));
@ -906,4 +898,37 @@ public class DistributionTests extends AbstractJettyHomeTest
assertFalse(Files.exists(jettyBase.resolve("resources/jetty-logging.properties")));
}
}
@Test
@EnabledForJreRange(min = JRE.JAVA_16)
public void testUnixDomain() throws Exception
{
String jettyVersion = System.getProperty("jettyVersion");
JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
.jettyVersion(jettyVersion)
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
.build();
try (JettyHomeTester.Run run1 = distribution.start("--add-modules=unixdomain-http"))
{
assertTrue(run1.awaitFor(10, TimeUnit.SECONDS));
assertEquals(0, run1.getExitValue());
int maxUnixDomainPathLength = 108;
Path path = Files.createTempFile("unix", ".sock");
if (path.normalize().toAbsolutePath().toString().length() > maxUnixDomainPathLength)
path = Files.createTempFile(Path.of("/tmp"), "unix", ".sock");
assertTrue(Files.deleteIfExists(path));
try (JettyHomeTester.Run run2 = distribution.start("jetty.unixdomain.path=" + path))
{
assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS));
ClientConnector connector = ClientConnector.forUnixDomain(path);
client = new HttpClient(new HttpClientTransportDynamic(connector));
client.start();
ContentResponse response = client.GET("http://localhost/path");
assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
}
}
}
}

View File

@ -17,7 +17,6 @@ import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@ -52,12 +51,10 @@ import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.unixsocket.client.HttpClientTransportOverUnixSockets;
import org.eclipse.jetty.unixsocket.server.UnixSocketConnector;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.SocketAddressResolver;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.Assumptions;
@ -65,7 +62,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.eclipse.jetty.http.client.Transport.UNIX_SOCKET;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TransportScenario
{
@ -86,20 +84,10 @@ public class TransportScenario
{
this.transport = transport;
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)
{
Files.delete(sockFile);
Path tmp = Paths.get("/tmp");
assumeTrue(Files.exists(tmp) && Files.isDirectory(tmp));
sockFile = Files.createTempFile(tmp, "unix", ".sock");
}
String dir = System.getProperty("jetty.unixdomain.dir");
assertNotNull(dir);
sockFile = Files.createTempFile(Path.of(dir), "unix_", ".sock");
assertTrue(sockFile.toAbsolutePath().toString().length() < UnixSocketConnector.MAX_UNIX_SOCKET_PATH_LENGTH, "Unix-Domain path too long");
Files.delete(sockFile);
// Disable UNIX_SOCKET due to jnr/jnr-unixsocket#69.