Skip to content

Commit

Permalink
Fixes #6043 - Reimplement UnixSocket support based on Java 16. (#6522)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
sbordet committed Aug 5, 2021
1 parent 8de7d55 commit 49a0845
Show file tree
Hide file tree
Showing 34 changed files with 1,303 additions and 169 deletions.
2 changes: 1 addition & 1 deletion Jenkinsfile
Expand Up @@ -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"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Jenkinsfile-autobahn
Expand Up @@ -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"
}
}

Expand Down
5 changes: 5 additions & 0 deletions documentation/jetty-documentation/pom.xml
Expand Up @@ -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>
Expand Down
Expand Up @@ -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].
Expand Down Expand Up @@ -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.

Expand Down
Expand Up @@ -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]:

Expand Down
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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`.

Expand All @@ -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`
Expand All @@ -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.
Expand Down
Expand Up @@ -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.

Expand All @@ -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]
----
Expand All @@ -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]]
Expand Down Expand Up @@ -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:

Expand Down Expand Up @@ -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.

Expand All @@ -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.

0 comments on commit 49a0845

Please sign in to comment.