org.eclipse.jetty.io.Transport Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995 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.io;
import java.io.IOException;
import java.net.SocketAddress;
import java.net.StandardProtocolFamily;
import java.net.UnixDomainSocketAddress;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.file.Path;
import java.util.Map;
import java.util.Objects;
import org.eclipse.jetty.util.thread.Scheduler;
/**
* The low-level transport used by clients.
* A high-level protocol such as HTTP/1.1 can be transported over a low-level
* protocol such as TCP/IP, Unix-Domain sockets, QUIC, shared memory, etc.
* This class defines the programming interface to implement low-level
* protocols, and useful implementations for commonly used low-level
* protocols such as TCP/IP or Unix-Domain sockets.
* Low-level transports may be layered; some of them maybe considered
* lower-level than others, but from the point of view of the high-level
* protocols they are all considered low-level.
* For example, QUIC is typically layered on top of the UDP/IP low-level
* {@code Transport}, but it may be layered on top Unix-Domain sockets,
* or on top of shared memory.
* As QUIC provides a reliable, ordered, stream-based transport, it may
* be seen as a replacement for TCP, and high-level protocols that need
* a reliable, ordered, stream-based transport may use either the non-layered
* TCP/IP or the layered QUIC over UDP/IP without noticing the difference.
* This makes possible to transport HTTP/1.1 over QUIC over Unix-Domain
* sockets, or HTTP/2 over QUIC over shared memory, etc.
*/
public interface Transport
{
/**
* The TCP/IP {@code Transport}.
*/
Transport TCP_IP = new TCPIP();
/**
* The UDP/IP {@code Transport}.
*/
Transport UDP_IP = new UDPIP();
/**
* @return whether this {@code Transport} is intrinsically secure.
*/
default boolean isIntrinsicallySecure()
{
return false;
}
/**
* Returns whether this {@code Transport} requires resolution of domain
* names.
* When domain name resolution is required, it must be performed by
* an external service, and the value returned by {@link #getSocketAddress()}
* is ignored, while the resolved socket address is eventually passed to
* {@link #connect(SocketAddress, Map)}.
* Otherwise, domain name resolution is not required, and the value returned
* by {@link #getSocketAddress()} is eventually passed to
* {@link #connect(SocketAddress, Map)}.
*
* @return whether this {@code Transport} requires domain names resolution
*/
default boolean requiresDomainNameResolution()
{
return false;
}
/**
* Establishes a connection to the given socket address.
* For {@code Transport}s that {@link #requiresDomainNameResolution()
* require domain name resolution}, this is the IP address resolved from
* the domain name.
* For {@code Transport}s that do not require domain name resolution
* (for example Unix-Domain sockets, or memory) this is the socket address
* to connect to.
*
* @param socketAddress the socket address to connect to
* @param context the context information to establish the connection
*/
default void connect(SocketAddress socketAddress, Map context)
{
}
/**
* @return the socket address to connect to in case domain name resolution is not required
*/
default SocketAddress getSocketAddress()
{
return null;
}
/**
* For {@code Transport}s that are based on sockets, or for {@code Transport}s
* that are layered on top of another {@code Transport} that is based on sockets,
* this method is invoked to create a new {@link SelectableChannel} used for the
* socket communication.
*
* @return a new {@link SelectableChannel} used for the socket communication,
* or {@code null} if the communication does not use sockets.
* @throws IOException if the {@link SelectableChannel} cannot be created
*/
default SelectableChannel newSelectableChannel() throws IOException
{
return null;
}
/**
* For {@code Transport}s that are based on sockets, or for {@code Transport}s
* that are layered on top of another {@code Transport} that is based on sockets,
* this method is invoked to create a new {@link EndPoint} that wraps the
* {@link SelectableChannel} created by {@link #newSelectableChannel()}.
*
* @param scheduler the {@link Scheduler}
* @param selector the {@link ManagedSelector}
* @param selectable the {@link SelectableChannel}
* @param selectionKey the {@link SelectionKey}
* @return a new {@link EndPoint}
*/
default EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, SelectableChannel selectable, SelectionKey selectionKey)
{
return null;
}
/**
* Creates a new {@link Connection} to be associated with the given low-level {@link EndPoint}.
* For non-layered {@code Transport}s such as TCP/IP, the {@link Connection} is typically
* that of the high-level protocol.
* For layered {@code Transport}s such as QUIC, the {@link Connection} is typically that of the
* layered {@code Transport}.
*
* @param endPoint the {@link EndPoint} to associate the {@link Connection} to
* @param context the context information to create the connection
* @return a new {@link Connection}
* @throws IOException if the {@link Connection} cannot be created
*/
default Connection newConnection(EndPoint endPoint, Map context) throws IOException
{
ClientConnectionFactory factory = (ClientConnectionFactory)context.get(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY);
return factory.newConnection(endPoint, context);
}
int hashCode();
boolean equals(Object obj);
/**
* Abstract implementation of {@code Transport} based on sockets.
*/
abstract class Socket implements Transport
{
@Override
public void connect(SocketAddress socketAddress, Map context)
{
ClientConnector connector = (ClientConnector)context.get(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY);
connector.connect(socketAddress, context);
}
@Override
public String toString()
{
return "%s@%x".formatted(getClass().getSimpleName(), hashCode());
}
}
/**
* Abstract implementation of {@code Transport} based on IP.
*/
abstract class IP extends Socket
{
@Override
public boolean requiresDomainNameResolution()
{
return true;
}
}
/**
* The TCP/IP {@code Transport}.
*/
class TCPIP extends IP
{
protected TCPIP()
{
// Do not instantiate, use the singleton.
}
@Override
public SelectableChannel newSelectableChannel() throws IOException
{
return SocketChannel.open();
}
@Override
public EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, SelectableChannel selectable, SelectionKey selectionKey)
{
return new SocketChannelEndPoint((SocketChannel)selectable, selector, selectionKey, scheduler);
}
}
/**
* The UDP/IP {@code Transport}.
*/
class UDPIP extends Transport.IP
{
protected UDPIP()
{
// Do not instantiate, use the singleton.
}
@Override
public SelectableChannel newSelectableChannel() throws IOException
{
return DatagramChannel.open();
}
@Override
public EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, SelectableChannel selectable, SelectionKey selectionKey)
{
return new DatagramChannelEndPoint((DatagramChannel)selectable, selector, selectionKey, scheduler);
}
}
/**
* Abstract implementation of {@code Transport} based on Unix-Domain sockets.
*/
abstract class Unix extends Socket
{
private final UnixDomainSocketAddress socketAddress;
protected Unix(Path path)
{
this.socketAddress = UnixDomainSocketAddress.of(path);
}
@Override
public SocketAddress getSocketAddress()
{
return socketAddress;
}
@Override
public int hashCode()
{
return Objects.hash(socketAddress);
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj instanceof Unix unix)
return Objects.equals(socketAddress, unix.socketAddress);
return false;
}
@Override
public String toString()
{
return "%s[%s]".formatted(super.toString(), socketAddress.getPath());
}
}
/**
* The stream Unix-Domain socket {@code Transport}.
*/
class TCPUnix extends Unix
{
public TCPUnix(Path path)
{
super(path);
}
@Override
public SelectableChannel newSelectableChannel() throws IOException
{
return SocketChannel.open(StandardProtocolFamily.UNIX);
}
@Override
public EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, SelectableChannel selectable, SelectionKey selectionKey)
{
return new SocketChannelEndPoint((SocketChannel)selectable, selector, selectionKey, scheduler);
}
}
/**
* The datagram Unix-Domain socket {@code Transport}.
*/
class UDPUnix extends Unix
{
public UDPUnix(Path path)
{
super(path);
}
@Override
public SelectableChannel newSelectableChannel() throws IOException
{
return DatagramChannel.open(StandardProtocolFamily.UNIX);
}
@Override
public EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, SelectableChannel selectable, SelectionKey selectionKey)
{
return new DatagramChannelEndPoint((DatagramChannel)selectable, selector, selectionKey, scheduler);
}
}
/**
* A wrapper for {@link Transport} instances to allow layering of {@code Transport}s.
*/
class Wrapper implements Transport
{
private final Transport wrapped;
public Wrapper(Transport wrapped)
{
this.wrapped = Objects.requireNonNull(wrapped);
}
public Transport getWrapped()
{
return wrapped;
}
public Transport unwrap()
{
Transport result = getWrapped();
while (true)
{
if (result instanceof Wrapper wrapper)
result = wrapper.getWrapped();
else
break;
}
return result;
}
@Override
public boolean isIntrinsicallySecure()
{
return wrapped.isIntrinsicallySecure();
}
@Override
public boolean requiresDomainNameResolution()
{
return wrapped.requiresDomainNameResolution();
}
@Override
public void connect(SocketAddress socketAddress, Map context)
{
wrapped.connect(socketAddress, context);
}
@Override
public SocketAddress getSocketAddress()
{
return wrapped.getSocketAddress();
}
@Override
public SelectableChannel newSelectableChannel() throws IOException
{
return wrapped.newSelectableChannel();
}
@Override
public EndPoint newEndPoint(Scheduler scheduler, ManagedSelector selector, SelectableChannel selectable, SelectionKey selectionKey)
{
return wrapped.newEndPoint(scheduler, selector, selectable, selectionKey);
}
@Override
public Connection newConnection(EndPoint endPoint, Map context) throws IOException
{
return wrapped.newConnection(endPoint, context);
}
@Override
public String toString()
{
return "%s@%x[%s]".formatted(getClass().getSimpleName(), hashCode(), getWrapped());
}
}
}