Merged branch 'jetty-10.0.x' into 'jetty-11.0.x'.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2021-08-13 17:54:41 +02:00
commit 27288cef9c
No known key found for this signature in database
GPG Key ID: 1677D141BCF3584D
17 changed files with 413 additions and 46 deletions

View File

@ -144,6 +144,7 @@ public class JettyIncludeExtension implements ExtensionRegistry
.map(line -> redact(line, run.getConfig().getMavenLocalRepository(), "/path/to/maven.repository")) .map(line -> redact(line, run.getConfig().getMavenLocalRepository(), "/path/to/maven.repository"))
.map(line -> redact(line, run.getConfig().getJettyHome().toString(), "/path/to/jetty.home")) .map(line -> redact(line, run.getConfig().getJettyHome().toString(), "/path/to/jetty.home"))
.map(line -> redact(line, run.getConfig().getJettyBase().toString(), "/path/to/jetty.base")) .map(line -> redact(line, run.getConfig().getJettyBase().toString(), "/path/to/jetty.base"))
.map(line -> regexpRedact(line, "(^| )[^ ]+/etc/jetty-halt\\.xml", ""))
.map(line -> redact(line, (String)document.getAttribute("project-version"), (String)document.getAttribute("version"))); .map(line -> redact(line, (String)document.getAttribute("project-version"), (String)document.getAttribute("version")));
lines = replace(lines, (String)attributes.get("replace")); lines = replace(lines, (String)attributes.get("replace"));
lines = delete(lines, (String)attributes.get("delete")); lines = delete(lines, (String)attributes.get("delete"));
@ -160,6 +161,13 @@ public class JettyIncludeExtension implements ExtensionRegistry
return line; return line;
} }
private String regexpRedact(String line, String regexp, String replacement)
{
if (regexp != null && replacement != null)
return line.replaceAll(regexp, replacement);
return line;
}
private Stream<String> replace(Stream<String> lines, String replace) private Stream<String> replace(Stream<String> lines, String replace)
{ {
if (replace == null) if (replace == null)
@ -178,8 +186,7 @@ public class JettyIncludeExtension implements ExtensionRegistry
if (delete == null) if (delete == null)
return lines; return lines;
Pattern regExp = Pattern.compile(delete); Pattern regExp = Pattern.compile(delete);
return lines.filter(line -> !regExp.matcher(line).find()) return lines.filter(line -> !regExp.matcher(line).find());
.filter(line -> !line.contains("jetty-halt.xml"));
} }
private Stream<String> denoteLineStart(Stream<String> lines) private Stream<String> denoteLineStart(Stream<String> lines)

View File

@ -396,6 +396,8 @@ When the `[exec]` section is present, the JVM running the Jetty start mechanism
This is necessary because JVM options such as `-Xmx` (that specifies the max JVM heap size) cannot be changed in a running JVM. This is necessary because JVM options such as `-Xmx` (that specifies the max JVM heap size) cannot be changed in a running JVM.
For an example, see xref:og-start-configure-custom-module-exec[this section]. For an example, see xref:og-start-configure-custom-module-exec[this section].
You can avoid that the Jetty start mechanism forks the second JVM, as explained in xref:og-start-configure-dry-run[this section].
[[og-modules-directive-jpms]] [[og-modules-directive-jpms]]
===== [jpms] ===== [jpms]

View File

@ -163,10 +163,15 @@ $ java -jar $JETTY_HOME/start.jar --add-modules=jvm
Since the module defines an `[exec]` section, it will fork _another_ JVM when Jetty is started. Since the module defines an `[exec]` section, it will fork _another_ JVM when Jetty is started.
This means that when you start Jetty, there will be _two_ JVMs running: one spawned by you when you run `java -jar $JETTY_HOME/start.jar`, and another spawned by the Jetty start mechanism with the JVM options you specified (that cannot be applied to an already running JVM). This means that when you start Jetty, there will be _two_ JVMs running: one created by you when you run `java -jar $JETTY_HOME/start.jar`, and another forked by the Jetty start mechanism with the JVM options you specified (that cannot be applied to an already running JVM).
Again, you can xref:og-start-configure-dry-run[display the JVM command line] to verify that it is correct. Again, you can xref:og-start-configure-dry-run[display the JVM command line] to verify that it is correct.
[TIP]
====
The second JVM forked by the Jetty start mechanism when one of the modules requires forking, for example a module that contains an `[exec]` section, may not be desirable, and may be avoided as explained in xref:og-start-configure-dry-run[this section].
====
[[og-start-configure-display]] [[og-start-configure-display]]
===== Displaying the Configuration ===== Displaying the Configuration
@ -205,7 +210,28 @@ Some option, such as `--jpms`, imply `--exec`, as it won't be possible to modify
To start Jetty without forking a second JVM, the `--dry-run` option can be used to generate a command line that is then executed so that starting Jetty only spawns one JVM. To start Jetty without forking a second JVM, the `--dry-run` option can be used to generate a command line that is then executed so that starting Jetty only spawns one JVM.
The `--dry-run` option is quite flexible and below you can find a few examples of how to use it to generate scripts or to create an arguments file that can be passed to the `java` executable. IMPORTANT: You can use the `--dry-run` option as explained below to avoid forking a second JVM when using modules that have the `[exec]` section, or the `--exec` option, or when using the `--jpms` option.
For example, using the `--dry-run` option with the `jvm.mod` introduced in xref:og-start-configure-custom-module-exec[this section] produces the following command line:
----
$ java -jar $JETTY_HOME/start.jar --dry-run
----
[source,options=nowrap]
----
include::jetty[setupModules="src/main/asciidoc/operations-guide/start/jvm.mod",setupArgs="--add-modules=http,jvm",args="--dry-run",replace="( ),$1\\\n"]
----
You can then run the generated command line.
For example, in the Linux `bash` shell you can run it by wrapping it into `$(\...)`:
----
$ $(java -jar $JETTY_HOME/start.jar --dry-run)
----
The `--dry-run` option is quite flexible and below you can find a few examples of how to use it to avoid forking a second JVM, or generating scripts or creating an arguments file that can be passed to (a possibly alternative) `java` executable.
To display the `java` executable used to start Jetty: To display the `java` executable used to start Jetty:
@ -304,7 +330,13 @@ $ java -jar $JETTY_HOME/start.jar --dry-run=##opts,path,main,args## > /tmp/jvm_c
$ /some/other/java @/tmp/jvm_cmd_line.txt $ /some/other/java @/tmp/jvm_cmd_line.txt
---- ----
Alternatively, they can be combined in a shell script: Using `--dry-run=opts,path,main,args` can be used to avoid that the Jetty start mechanism forks a second JVM when using modules that require forking:
----
$ java $(java -jar $JETTY_HOME/start.jar --dry-run=opts,path,main,args)
----
The output of different `--dry-run` executions can be creatively combined in a shell script:
[source,subs=quotes] [source,subs=quotes]
---- ----

View File

@ -923,8 +923,10 @@ public class HttpClient extends ContainerLifeCycle
/** /**
* @return whether TCP_NODELAY is enabled * @return whether TCP_NODELAY is enabled
* @deprecated use {@link ClientConnector#isTCPNoDelay()} instead
*/ */
@ManagedAttribute(value = "Whether the TCP_NODELAY option is enabled", name = "tcpNoDelay") @ManagedAttribute(value = "Whether the TCP_NODELAY option is enabled", name = "tcpNoDelay")
@Deprecated
public boolean isTCPNoDelay() public boolean isTCPNoDelay()
{ {
return tcpNoDelay; return tcpNoDelay;
@ -933,7 +935,9 @@ public class HttpClient extends ContainerLifeCycle
/** /**
* @param tcpNoDelay whether TCP_NODELAY is enabled * @param tcpNoDelay whether TCP_NODELAY is enabled
* @see java.net.Socket#setTcpNoDelay(boolean) * @see java.net.Socket#setTcpNoDelay(boolean)
* @deprecated use {@link ClientConnector#setTCPNoDelay(boolean)} instead
*/ */
@Deprecated
public void setTCPNoDelay(boolean tcpNoDelay) public void setTCPNoDelay(boolean tcpNoDelay)
{ {
this.tcpNoDelay = tcpNoDelay; this.tcpNoDelay = tcpNoDelay;

View File

@ -73,6 +73,7 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
@ -1909,6 +1910,45 @@ public class HttpClientTest extends AbstractHttpClientServerTest
assertTrue(serverOnErrorLatch.await(5, TimeUnit.SECONDS), "serverOnErrorLatch didn't finish"); assertTrue(serverOnErrorLatch.await(5, TimeUnit.SECONDS), "serverOnErrorLatch didn't finish");
} }
@ParameterizedTest
@ArgumentsSource(ScenarioProvider.class)
public void testBindAddress(Scenario scenario) throws Exception
{
String bindAddress = "127.0.0.2";
start(scenario, new EmptyServerHandler()
{
@Override
protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
{
assertEquals(bindAddress, request.getRemoteAddr());
}
});
client.setBindAddress(new InetSocketAddress(bindAddress, 0));
CountDownLatch latch = new CountDownLatch(1);
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.path("/1")
.onRequestBegin(r ->
{
client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.path("/2")
.send(result ->
{
assertTrue(result.isSucceeded());
assertEquals(HttpStatus.OK_200, result.getResponse().getStatus());
latch.countDown();
});
})
.timeout(5, TimeUnit.SECONDS)
.send();
assertTrue(latch.await(5, TimeUnit.SECONDS));
assertEquals(HttpStatus.OK_200, response.getStatus());
}
private void assertCopyRequest(Request original) private void assertCopyRequest(Request original)
{ {
Request copy = client.copyRequest((HttpRequest)original, original.getURI()); Request copy = client.copyRequest((HttpRequest)original, original.getURI());

View File

@ -66,7 +66,8 @@ NAME=$(echo $(basename $0) | sed -e 's/^[SK][0-9]*//' -e 's/\.sh$//')
# <Arg><Property name="jetty.home" default="."/>/webapps/jetty.war</Arg> # <Arg><Property name="jetty.home" default="."/>/webapps/jetty.war</Arg>
# #
# JETTY_BASE # JETTY_BASE
# Where your Jetty base directory is. If not set, the value from # Where your Jetty base directory is. If not set, then the currently
# directory is checked, otherwise the value from
# $JETTY_HOME will be used. # $JETTY_HOME will be used.
# #
# JETTY_RUN # JETTY_RUN
@ -238,7 +239,6 @@ then
fi fi
fi fi
################################################## ##################################################
# No JETTY_HOME yet? We're out of luck! # No JETTY_HOME yet? We're out of luck!
################################################## ##################################################
@ -247,20 +247,23 @@ if [ -z "$JETTY_HOME" ]; then
exit 1 exit 1
fi fi
RUN_DIR=$(pwd)
cd "$JETTY_HOME" cd "$JETTY_HOME"
JETTY_HOME=$PWD JETTY_HOME=$(pwd)
################################################## ##################################################
# Set JETTY_BASE # Set JETTY_BASE
################################################## ##################################################
export JETTY_BASE
if [ -z "$JETTY_BASE" ]; then if [ -z "$JETTY_BASE" ]; then
JETTY_BASE=$JETTY_HOME if [ -d "$RUN_DIR/start.d" -o -f "$RUN_DIR/start.ini" ]; then
JETTY_BASE=$RUN_DIR
else
JETTY_BASE=$JETTY_HOME
fi
fi fi
cd "$JETTY_BASE" cd "$JETTY_BASE"
JETTY_BASE=$PWD JETTY_BASE=$(pwd)
##################################################### #####################################################
# Check that jetty is where we think it is # Check that jetty is where we think it is
@ -430,7 +433,7 @@ case "`uname`" in
CYGWIN*) JETTY_START="`cygpath -w $JETTY_START`";; CYGWIN*) JETTY_START="`cygpath -w $JETTY_START`";;
esac esac
RUN_ARGS=(${JAVA_OPTIONS[@]} -jar "$JETTY_START" ${JETTY_ARGS[*]}) RUN_ARGS=$(echo $JAVA_OPTIONS ; "$JAVA" -jar "$JETTY_START" --dry-run=opts,path,main,args ${JETTY_ARGS[*]})
RUN_CMD=("$JAVA" ${RUN_ARGS[@]}) RUN_CMD=("$JAVA" ${RUN_ARGS[@]})
##################################################### #####################################################

View File

@ -93,6 +93,8 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
client.setInputBufferSize(httpClient.getResponseBufferSize()); client.setInputBufferSize(httpClient.getResponseBufferSize());
client.setUseInputDirectByteBuffers(httpClient.isUseInputDirectByteBuffers()); client.setUseInputDirectByteBuffers(httpClient.isUseInputDirectByteBuffers());
client.setUseOutputDirectByteBuffers(httpClient.isUseOutputDirectByteBuffers()); client.setUseOutputDirectByteBuffers(httpClient.isUseOutputDirectByteBuffers());
client.setConnectBlocking(httpClient.isConnectBlocking());
client.setBindAddress(httpClient.getBindAddress());
} }
addBean(client); addBean(client);
super.doStart(); super.doStart();

View File

@ -18,6 +18,7 @@ import java.net.InetSocketAddress;
import java.net.ProtocolFamily; import java.net.ProtocolFamily;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.net.SocketException; import java.net.SocketException;
import java.net.SocketOption;
import java.net.StandardProtocolFamily; import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions; import java.net.StandardSocketOptions;
import java.nio.channels.SelectableChannel; import java.nio.channels.SelectableChannel;
@ -33,6 +34,8 @@ import java.util.concurrent.Executor;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.JavaVersion; import org.eclipse.jetty.util.JavaVersion;
import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.QueuedThreadPool;
@ -41,6 +44,32 @@ import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/**
* <p>The client-side component that connects to server sockets.</p>
* <p>ClientConnector delegates the handling of {@link SocketChannel}s
* to a {@link SelectorManager}, and centralizes the configuration of
* necessary components such as the executor, the scheduler, etc.</p>
* <p>ClientConnector offers a low-level API that can be used to
* connect {@link SocketChannel}s to listening servers via the
* {@link #connect(SocketAddress, Map)} method.</p>
* <p>However, a ClientConnector instance is typically just configured
* and then passed to an HttpClient transport, so that applications
* can use high-level APIs to make HTTP requests to servers:</p>
* <pre>
* // Create a ClientConnector instance.
* ClientConnector connector = new ClientConnector();
*
* // Configure the ClientConnector.
* connector.setSelectors(1);
* connector.setSslContextFactory(new SslContextFactory.Client());
*
* // Pass it to the HttpClient transport.
* HttpClientTransport transport = new HttpClientTransportDynamic(connector);
* HttpClient httpClient = new HttpClient(transport);
* httpClient.start();
* </pre>
*/
@ManagedObject
public class ClientConnector extends ContainerLifeCycle public class ClientConnector extends ContainerLifeCycle
{ {
public static final String CLIENT_CONNECTOR_CONTEXT_KEY = "org.eclipse.jetty.client.connector"; public static final String CLIENT_CONNECTOR_CONTEXT_KEY = "org.eclipse.jetty.client.connector";
@ -49,6 +78,12 @@ public class ClientConnector extends ContainerLifeCycle
public static final String CONNECTION_PROMISE_CONTEXT_KEY = CLIENT_CONNECTOR_CONTEXT_KEY + ".connectionPromise"; public static final String CONNECTION_PROMISE_CONTEXT_KEY = CLIENT_CONNECTOR_CONTEXT_KEY + ".connectionPromise";
private static final Logger LOG = LoggerFactory.getLogger(ClientConnector.class); private static final Logger LOG = LoggerFactory.getLogger(ClientConnector.class);
/**
* <p>Creates a ClientConnector configured to connect via Unix-Domain sockets to the given Unix-Domain path</p>
*
* @param path the Unix-Domain path to connect to
* @return a ClientConnector that connects to the given Unix-Domain path
*/
public static ClientConnector forUnixDomain(Path path) public static ClientConnector forUnixDomain(Path path)
{ {
return new ClientConnector(SocketChannelWithAddress.Factory.forUnixDomain(path)); return new ClientConnector(SocketChannelWithAddress.Factory.forUnixDomain(path));
@ -65,7 +100,11 @@ public class ClientConnector extends ContainerLifeCycle
private Duration connectTimeout = Duration.ofSeconds(5); private Duration connectTimeout = Duration.ofSeconds(5);
private Duration idleTimeout = Duration.ofSeconds(30); private Duration idleTimeout = Duration.ofSeconds(30);
private SocketAddress bindAddress; private SocketAddress bindAddress;
private boolean tcpNoDelay = true;
private boolean reuseAddress = true; private boolean reuseAddress = true;
private boolean reusePort;
private int receiveBufferSize = -1;
private int sendBufferSize = -1;
public ClientConnector() public ClientConnector()
{ {
@ -129,6 +168,10 @@ public class ClientConnector extends ContainerLifeCycle
this.sslContextFactory = sslContextFactory; this.sslContextFactory = sslContextFactory;
} }
/**
* @return the number of NIO selectors
*/
@ManagedAttribute("The number of NIO selectors")
public int getSelectors() public int getSelectors()
{ {
return selectors; return selectors;
@ -141,6 +184,10 @@ public class ClientConnector extends ContainerLifeCycle
this.selectors = selectors; this.selectors = selectors;
} }
/**
* @return whether {@link #connect(SocketAddress, Map)} operations are performed in blocking mode
*/
@ManagedAttribute("Whether connect operations are performed in blocking mode")
public boolean isConnectBlocking() public boolean isConnectBlocking()
{ {
return connectBlocking; return connectBlocking;
@ -151,6 +198,10 @@ public class ClientConnector extends ContainerLifeCycle
this.connectBlocking = connectBlocking; this.connectBlocking = connectBlocking;
} }
/**
* @return the timeout of {@link #connect(SocketAddress, Map)} operations
*/
@ManagedAttribute("The timeout of connect operations")
public Duration getConnectTimeout() public Duration getConnectTimeout()
{ {
return connectTimeout; return connectTimeout;
@ -163,6 +214,10 @@ public class ClientConnector extends ContainerLifeCycle
selectorManager.setConnectTimeout(connectTimeout.toMillis()); selectorManager.setConnectTimeout(connectTimeout.toMillis());
} }
/**
* @return the max duration for which a connection can be idle (that is, without traffic of bytes in either direction)
*/
@ManagedAttribute("The duration for which a connection can be idle")
public Duration getIdleTimeout() public Duration getIdleTimeout()
{ {
return idleTimeout; return idleTimeout;
@ -173,26 +228,120 @@ public class ClientConnector extends ContainerLifeCycle
this.idleTimeout = idleTimeout; this.idleTimeout = idleTimeout;
} }
/**
* @return the address to bind a socket to before the connect operation
*/
@ManagedAttribute("The socket address to bind sockets to before the connect operation")
public SocketAddress getBindAddress() public SocketAddress getBindAddress()
{ {
return bindAddress; return bindAddress;
} }
/**
* <p>Sets the bind address of sockets before the connect operation.</p>
* <p>In multi-homed hosts, you may want to connect from a specific address:</p>
* <pre>
* clientConnector.setBindAddress(new InetSocketAddress("127.0.0.2", 0));
* </pre>
* <p>Note the use of the port {@code 0} to indicate that a different ephemeral port
* should be used for each different connection.</p>
* <p>In the rare cases where you want to use the same port for all connections,
* you must also call {@link #setReusePort(boolean) setReusePort(true)}.</p>
*
* @param bindAddress the socket address to bind to before the connect operation
*/
public void setBindAddress(SocketAddress bindAddress) public void setBindAddress(SocketAddress bindAddress)
{ {
this.bindAddress = bindAddress; this.bindAddress = bindAddress;
} }
/**
* @return whether small TCP packets are sent without delay
*/
@ManagedAttribute("Whether small TCP packets are sent without delay")
public boolean isTCPNoDelay()
{
return tcpNoDelay;
}
public void setTCPNoDelay(boolean tcpNoDelay)
{
this.tcpNoDelay = tcpNoDelay;
}
/**
* @return whether rebinding is allowed with sockets in tear-down states
*/
@ManagedAttribute("Whether rebinding is allowed with sockets in tear-down states")
public boolean getReuseAddress() public boolean getReuseAddress()
{ {
return reuseAddress; return reuseAddress;
} }
/**
* <p>Sets whether it is allowed to bind a socket to a socket address
* that may be in use by another socket in tear-down state, for example
* in TIME_WAIT state.</p>
* <p>This is useful when ClientConnector is restarted: an existing connection
* may still be using a network address (same host and same port) that is also
* chosen for a new connection.</p>
*
* @param reuseAddress whether rebinding is allowed with sockets in tear-down states
* @see #setReusePort(boolean)
*/
public void setReuseAddress(boolean reuseAddress) public void setReuseAddress(boolean reuseAddress)
{ {
this.reuseAddress = reuseAddress; this.reuseAddress = reuseAddress;
} }
/**
* @return whether binding to same host and port is allowed
*/
@ManagedAttribute("Whether binding to same host and port is allowed")
public boolean isReusePort()
{
return reusePort;
}
/**
* <p>Sets whether it is allowed to bind multiple sockets to the same
* socket address (same host and same port).</p>
*
* @param reusePort whether binding to same host and port is allowed
*/
public void setReusePort(boolean reusePort)
{
this.reusePort = reusePort;
}
/**
* @return the receive buffer size in bytes, or -1 for the default value
*/
@ManagedAttribute("The receive buffer size in bytes")
public int getReceiveBufferSize()
{
return receiveBufferSize;
}
public void setReceiveBufferSize(int receiveBufferSize)
{
this.receiveBufferSize = receiveBufferSize;
}
/**
* @return the send buffer size in bytes, or -1 for the default value
*/
@ManagedAttribute("The send buffer size in bytes")
public int getSendBufferSize()
{
return sendBufferSize;
}
public void setSendBufferSize(int sendBufferSize)
{
this.sendBufferSize = sendBufferSize;
}
@Override @Override
protected void doStart() throws Exception protected void doStart() throws Exception
{ {
@ -246,10 +395,12 @@ public class ClientConnector extends ContainerLifeCycle
SocketChannelWithAddress channelWithAddress = factory.newSocketChannelWithAddress(address, context); SocketChannelWithAddress channelWithAddress = factory.newSocketChannelWithAddress(address, context);
channel = channelWithAddress.getSocketChannel(); channel = channelWithAddress.getSocketChannel();
address = channelWithAddress.getSocketAddress(); address = channelWithAddress.getSocketAddress();
configure(channel);
SocketAddress bindAddress = getBindAddress(); SocketAddress bindAddress = getBindAddress();
if (bindAddress != null) if (bindAddress != null)
bind(channel, bindAddress); bind(channel, bindAddress);
configure(channel);
boolean connected = true; boolean connected = true;
boolean blocking = isConnectBlocking() && address instanceof InetSocketAddress; boolean blocking = isConnectBlocking() && address instanceof InetSocketAddress;
@ -306,33 +457,36 @@ public class ClientConnector extends ContainerLifeCycle
} }
} }
private void bind(SocketChannel channel, SocketAddress bindAddress) private void bind(SocketChannel channel, SocketAddress bindAddress) throws IOException
{ {
try if (LOG.isDebugEnabled())
{ LOG.debug("Binding {} to {}", channel, bindAddress);
boolean reuseAddress = getReuseAddress(); channel.bind(bindAddress);
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 protected void configure(SocketChannel channel) throws IOException
{
setSocketOption(channel, StandardSocketOptions.TCP_NODELAY, isTCPNoDelay());
setSocketOption(channel, StandardSocketOptions.SO_REUSEADDR, getReuseAddress());
setSocketOption(channel, StandardSocketOptions.SO_REUSEPORT, isReusePort());
int receiveBufferSize = getReceiveBufferSize();
if (receiveBufferSize >= 0)
setSocketOption(channel, StandardSocketOptions.SO_RCVBUF, receiveBufferSize);
int sendBufferSize = getSendBufferSize();
if (sendBufferSize >= 0)
setSocketOption(channel, StandardSocketOptions.SO_SNDBUF, sendBufferSize);
}
private <T> void setSocketOption(SocketChannel channel, SocketOption<T> option, T value)
{ {
try try
{ {
channel.setOption(StandardSocketOptions.TCP_NODELAY, true); channel.setOption(option, value);
} }
catch (Throwable x) catch (Throwable x)
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Could not configure {}", channel); LOG.debug("Could not configure {} to {} on {}", option, value, channel);
} }
} }

View File

@ -39,6 +39,7 @@
<Set name="acceptorPriorityDelta" property="jetty.http.acceptorPriorityDelta" /> <Set name="acceptorPriorityDelta" property="jetty.http.acceptorPriorityDelta" />
<Set name="acceptQueueSize" property="jetty.http.acceptQueueSize" /> <Set name="acceptQueueSize" property="jetty.http.acceptQueueSize" />
<Set name="reuseAddress"><Property name="jetty.http.reuseAddress" default="true"/></Set> <Set name="reuseAddress"><Property name="jetty.http.reuseAddress" default="true"/></Set>
<Set name="reusePort"><Property name="jetty.http.reusePort" default="false"/></Set>
<Set name="acceptedTcpNoDelay"><Property name="jetty.http.acceptedTcpNoDelay" default="true"/></Set> <Set name="acceptedTcpNoDelay"><Property name="jetty.http.acceptedTcpNoDelay" default="true"/></Set>
<Set name="acceptedReceiveBufferSize" property="jetty.http.acceptedReceiveBufferSize" /> <Set name="acceptedReceiveBufferSize" property="jetty.http.acceptedReceiveBufferSize" />
<Set name="acceptedSendBufferSize" property="jetty.http.acceptedSendBufferSize" /> <Set name="acceptedSendBufferSize" property="jetty.http.acceptedSendBufferSize" />

View File

@ -32,6 +32,7 @@
<Set name="acceptorPriorityDelta" property="jetty.ssl.acceptorPriorityDelta"/> <Set name="acceptorPriorityDelta" property="jetty.ssl.acceptorPriorityDelta"/>
<Set name="acceptQueueSize" property="jetty.ssl.acceptQueueSize"/> <Set name="acceptQueueSize" property="jetty.ssl.acceptQueueSize"/>
<Set name="reuseAddress"><Property name="jetty.ssl.reuseAddress" default="true"/></Set> <Set name="reuseAddress"><Property name="jetty.ssl.reuseAddress" default="true"/></Set>
<Set name="reusePort"><Property name="jetty.ssl.reusePort" default="false"/></Set>
<Set name="acceptedTcpNoDelay"><Property name="jetty.ssl.acceptedTcpNoDelay" default="true"/></Set> <Set name="acceptedTcpNoDelay"><Property name="jetty.ssl.acceptedTcpNoDelay" default="true"/></Set>
<Set name="acceptedReceiveBufferSize" property="jetty.ssl.acceptedReceiveBufferSize" /> <Set name="acceptedReceiveBufferSize" property="jetty.ssl.acceptedReceiveBufferSize" />
<Set name="acceptedSendBufferSize" property="jetty.ssl.acceptedSendBufferSize" /> <Set name="acceptedSendBufferSize" property="jetty.ssl.acceptedSendBufferSize" />

View File

@ -40,6 +40,9 @@ etc/jetty-http.xml
## Whether to enable the SO_REUSEADDR socket option. ## Whether to enable the SO_REUSEADDR socket option.
# jetty.http.reuseAddress=true # jetty.http.reuseAddress=true
## Whether to enable the SO_REUSEPORT socket option.
# jetty.http.reusePort=false
## Whether to enable the TCP_NODELAY socket option on accepted sockets. ## Whether to enable the TCP_NODELAY socket option on accepted sockets.
# jetty.http.acceptedTcpNoDelay=true # jetty.http.acceptedTcpNoDelay=true

View File

@ -42,6 +42,9 @@ etc/jetty-ssl-context.xml
## Whether to enable the SO_REUSEADDR socket option. ## Whether to enable the SO_REUSEADDR socket option.
# jetty.ssl.reuseAddress=true # jetty.ssl.reuseAddress=true
## Whether to enable the SO_REUSEPORT socket option.
# jetty.ssl.reusePort=false
## Whether to enable the TCP_NODELAY socket option on accepted sockets. ## Whether to enable the TCP_NODELAY socket option on accepted sockets.
# jetty.ssl.acceptedTcpNoDelay=true # jetty.ssl.acceptedTcpNoDelay=true

View File

@ -19,6 +19,7 @@ import java.net.InetSocketAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
import java.net.StandardSocketOptions;
import java.nio.channels.Channel; import java.nio.channels.Channel;
import java.nio.channels.SelectableChannel; import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey; import java.nio.channels.SelectionKey;
@ -77,6 +78,7 @@ public class ServerConnector extends AbstractNetworkConnector
private volatile int _localPort = -1; private volatile int _localPort = -1;
private volatile int _acceptQueueSize = 0; private volatile int _acceptQueueSize = 0;
private volatile boolean _reuseAddress = true; private volatile boolean _reuseAddress = true;
private volatile boolean _reusePort = false;
private volatile boolean _acceptedTcpNoDelay = true; private volatile boolean _acceptedTcpNoDelay = true;
private volatile int _acceptedReceiveBufferSize = -1; private volatile int _acceptedReceiveBufferSize = -1;
private volatile int _acceptedSendBufferSize = -1; private volatile int _acceptedSendBufferSize = -1;
@ -332,8 +334,9 @@ public class ServerConnector extends AbstractNetworkConnector
serverChannel = ServerSocketChannel.open(); serverChannel = ServerSocketChannel.open();
try try
{ {
serverChannel.socket().setReuseAddress(getReuseAddress()); serverChannel.setOption(StandardSocketOptions.SO_REUSEADDR, getReuseAddress());
serverChannel.socket().bind(bindAddress, getAcceptQueueSize()); serverChannel.setOption(StandardSocketOptions.SO_REUSEPORT, isReusePort());
serverChannel.bind(bindAddress, getAcceptQueueSize());
} }
catch (Throwable e) catch (Throwable e)
{ {
@ -450,7 +453,7 @@ public class ServerConnector extends AbstractNetworkConnector
} }
/** /**
* @return whether the server socket reuses addresses * @return whether rebinding the server socket is allowed with sockets in tear-down states
* @see ServerSocket#getReuseAddress() * @see ServerSocket#getReuseAddress()
*/ */
@ManagedAttribute("Server Socket SO_REUSEADDR") @ManagedAttribute("Server Socket SO_REUSEADDR")
@ -460,7 +463,7 @@ public class ServerConnector extends AbstractNetworkConnector
} }
/** /**
* @param reuseAddress whether the server socket reuses addresses * @param reuseAddress whether rebinding the server socket is allowed with sockets in tear-down states
* @see ServerSocket#setReuseAddress(boolean) * @see ServerSocket#setReuseAddress(boolean)
*/ */
public void setReuseAddress(boolean reuseAddress) public void setReuseAddress(boolean reuseAddress)
@ -468,6 +471,23 @@ public class ServerConnector extends AbstractNetworkConnector
_reuseAddress = reuseAddress; _reuseAddress = reuseAddress;
} }
/**
* @return whether it is allowed to bind multiple server sockets to the same host and port
*/
@ManagedAttribute("Server Socket SO_REUSEPORT")
public boolean isReusePort()
{
return _reusePort;
}
/**
* @param reusePort whether it is allowed to bind multiple server sockets to the same host and port
*/
public void setReusePort(boolean reusePort)
{
_reusePort = reusePort;
}
/** /**
* @return whether the accepted socket gets {@link java.net.SocketOptions#TCP_NODELAY TCP_NODELAY} enabled. * @return whether the accepted socket gets {@link java.net.SocketOptions#TCP_NODELAY TCP_NODELAY} enabled.
* @see Socket#getTcpNoDelay() * @see Socket#getTcpNoDelay()

View File

@ -25,6 +25,7 @@ import java.net.Socket;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.channels.ServerSocketChannel; import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
@ -32,6 +33,9 @@ import java.util.concurrent.atomic.AtomicLong;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.SocketChannelEndPoint; import org.eclipse.jetty.io.SocketChannelEndPoint;
import org.eclipse.jetty.logging.StacklessLogging; import org.eclipse.jetty.logging.StacklessLogging;
@ -50,6 +54,7 @@ import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ -208,6 +213,59 @@ public class ServerConnectorTest
} }
} }
@Test
public void testReusePort() throws Exception
{
int port;
try (ServerSocket server = new ServerSocket())
{
server.setReuseAddress(true);
server.bind(new InetSocketAddress("localhost", 0));
port = server.getLocalPort();
}
Server server = new Server();
try
{
// Two connectors listening on the same port.
ServerConnector connector1 = new ServerConnector(server, 1, 1);
connector1.setReuseAddress(true);
connector1.setReusePort(true);
connector1.setPort(port);
server.addConnector(connector1);
ServerConnector connector2 = new ServerConnector(server, 1, 1);
connector2.setReuseAddress(true);
connector2.setReusePort(true);
connector2.setPort(port);
server.addConnector(connector2);
server.setHandler(new AbstractHandler()
{
@Override
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
{
jettyRequest.setHandled(true);
}
});
server.start();
try (SocketChannel client = SocketChannel.open(new InetSocketAddress("localhost", port)))
{
HttpTester.Request request = HttpTester.newRequest();
request.put(HttpHeader.HOST, "localhost");
client.write(request.generate());
HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(client));
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
}
}
finally
{
server.stop();
}
}
@Test @Test
public void testAddFirstConnectionFactory() public void testAddFirstConnectionFactory()
{ {

View File

@ -30,6 +30,7 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.stream.Collectors;
import org.eclipse.jetty.start.Props.Prop; import org.eclipse.jetty.start.Props.Prop;
import org.eclipse.jetty.start.config.CommandLineConfigSource; import org.eclipse.jetty.start.config.CommandLineConfigSource;
@ -467,6 +468,15 @@ public class Main
{ {
CommandLineBuilder cmd = args.getMainArgs(StartArgs.ALL_PARTS); CommandLineBuilder cmd = args.getMainArgs(StartArgs.ALL_PARTS);
cmd.debug(); cmd.debug();
List<String> execModules = args.getEnabledModules().stream()
.map(name -> args.getAllModules().get(name))
// Keep only the forking modules.
.filter(module -> !module.getJvmArgs().isEmpty())
.map(Module::getName)
.collect(Collectors.toList());
StartLog.warn("Forking second JVM due to forking module(s): %s. Use --dry-run to generate the command line to avoid forking.", execModules);
ProcessBuilder pbuilder = new ProcessBuilder(cmd.getArgs()); ProcessBuilder pbuilder = new ProcessBuilder(cmd.getArgs());
StartLog.endStartLog(); StartLog.endStartLog();
final Process process = pbuilder.start(); final Process process = pbuilder.start();

View File

@ -302,7 +302,7 @@ public class StartArgs
// Jetty Environment // Jetty Environment
System.out.println(); System.out.println();
System.out.println("Jetty Environment:"); System.out.println("Jetty Environment:");
System.out.println("-----------------"); System.out.println("------------------");
dumpProperty(JETTY_VERSION_KEY); dumpProperty(JETTY_VERSION_KEY);
dumpProperty(JETTY_TAG_NAME_KEY); dumpProperty(JETTY_TAG_NAME_KEY);
dumpProperty(JETTY_BUILDNUM_KEY); dumpProperty(JETTY_BUILDNUM_KEY);
@ -330,26 +330,20 @@ public class StartArgs
public void dumpJvmArgs() public void dumpJvmArgs()
{ {
System.out.println();
System.out.println("JVM Arguments:");
System.out.println("--------------");
if (jvmArgs.isEmpty()) if (jvmArgs.isEmpty())
{
System.out.println(" (no jvm args specified)");
return; return;
}
System.out.println();
System.out.println("Forked JVM Arguments:");
System.out.println("---------------------");
for (String jvmArgKey : jvmArgs) for (String jvmArgKey : jvmArgs)
{ {
String value = System.getProperty(jvmArgKey); String value = System.getProperty(jvmArgKey);
if (value != null) if (value != null)
{
System.out.printf(" %s = %s%n", jvmArgKey, value); System.out.printf(" %s = %s%n", jvmArgKey, value);
}
else else
{
System.out.printf(" %s%n", jvmArgKey); System.out.printf(" %s%n", jvmArgKey);
}
} }
} }

View File

@ -931,4 +931,37 @@ public class DistributionTests extends AbstractJettyHomeTest
} }
} }
} }
@Test
public void testModuleWithExecEmitsWarning() throws Exception
{
String jettyVersion = System.getProperty("jettyVersion");
JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
.jettyVersion(jettyVersion)
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
.build();
Path jettyBase = distribution.getJettyBase();
Path jettyBaseModules = jettyBase.resolve("modules");
Files.createDirectories(jettyBaseModules);
Path execModule = jettyBaseModules.resolve("exec.mod");
String module = "" +
"[exec]\n" +
"--show-version";
Files.write(execModule, List.of(module), StandardOpenOption.CREATE);
try (JettyHomeTester.Run run1 = distribution.start(List.of("--add-modules=http,exec")))
{
assertTrue(run1.awaitFor(10, TimeUnit.SECONDS));
assertEquals(0, run1.getExitValue());
int port = distribution.freePort();
try (JettyHomeTester.Run run2 = distribution.start("jetty.http.port=" + port))
{
assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS));
assertTrue(run2.getLogs().stream()
.anyMatch(log -> log.contains("WARN") && log.contains("Forking")));
}
}
}
} }