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:
commit
27288cef9c
|
@ -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().getJettyHome().toString(), "/path/to/jetty.home"))
|
||||
.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")));
|
||||
lines = replace(lines, (String)attributes.get("replace"));
|
||||
lines = delete(lines, (String)attributes.get("delete"));
|
||||
|
@ -160,6 +161,13 @@ public class JettyIncludeExtension implements ExtensionRegistry
|
|||
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)
|
||||
{
|
||||
if (replace == null)
|
||||
|
@ -178,8 +186,7 @@ public class JettyIncludeExtension implements ExtensionRegistry
|
|||
if (delete == null)
|
||||
return lines;
|
||||
Pattern regExp = Pattern.compile(delete);
|
||||
return lines.filter(line -> !regExp.matcher(line).find())
|
||||
.filter(line -> !line.contains("jetty-halt.xml"));
|
||||
return lines.filter(line -> !regExp.matcher(line).find());
|
||||
}
|
||||
|
||||
private Stream<String> denoteLineStart(Stream<String> lines)
|
||||
|
|
|
@ -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.
|
||||
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]]
|
||||
===== [jpms]
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
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.
|
||||
|
||||
[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]]
|
||||
===== 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.
|
||||
|
||||
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:
|
||||
|
||||
|
@ -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
|
||||
----
|
||||
|
||||
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]
|
||||
----
|
||||
|
|
|
@ -923,8 +923,10 @@ public class HttpClient extends ContainerLifeCycle
|
|||
|
||||
/**
|
||||
* @return whether TCP_NODELAY is enabled
|
||||
* @deprecated use {@link ClientConnector#isTCPNoDelay()} instead
|
||||
*/
|
||||
@ManagedAttribute(value = "Whether the TCP_NODELAY option is enabled", name = "tcpNoDelay")
|
||||
@Deprecated
|
||||
public boolean isTCPNoDelay()
|
||||
{
|
||||
return tcpNoDelay;
|
||||
|
@ -933,7 +935,9 @@ public class HttpClient extends ContainerLifeCycle
|
|||
/**
|
||||
* @param tcpNoDelay whether TCP_NODELAY is enabled
|
||||
* @see java.net.Socket#setTcpNoDelay(boolean)
|
||||
* @deprecated use {@link ClientConnector#setTCPNoDelay(boolean)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public void setTCPNoDelay(boolean tcpNoDelay)
|
||||
{
|
||||
this.tcpNoDelay = tcpNoDelay;
|
||||
|
|
|
@ -73,6 +73,7 @@ import org.eclipse.jetty.http.HttpHeader;
|
|||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
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");
|
||||
}
|
||||
|
||||
@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)
|
||||
{
|
||||
Request copy = client.copyRequest((HttpRequest)original, original.getURI());
|
||||
|
|
|
@ -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>
|
||||
#
|
||||
# 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_RUN
|
||||
|
@ -238,7 +239,6 @@ then
|
|||
fi
|
||||
fi
|
||||
|
||||
|
||||
##################################################
|
||||
# No JETTY_HOME yet? We're out of luck!
|
||||
##################################################
|
||||
|
@ -247,20 +247,23 @@ if [ -z "$JETTY_HOME" ]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
RUN_DIR=$(pwd)
|
||||
cd "$JETTY_HOME"
|
||||
JETTY_HOME=$PWD
|
||||
|
||||
JETTY_HOME=$(pwd)
|
||||
|
||||
##################################################
|
||||
# Set JETTY_BASE
|
||||
##################################################
|
||||
export JETTY_BASE
|
||||
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
|
||||
|
||||
cd "$JETTY_BASE"
|
||||
JETTY_BASE=$PWD
|
||||
|
||||
JETTY_BASE=$(pwd)
|
||||
|
||||
#####################################################
|
||||
# Check that jetty is where we think it is
|
||||
|
@ -430,7 +433,7 @@ case "`uname`" in
|
|||
CYGWIN*) JETTY_START="`cygpath -w $JETTY_START`";;
|
||||
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[@]})
|
||||
|
||||
#####################################################
|
||||
|
|
|
@ -93,6 +93,8 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
|
|||
client.setInputBufferSize(httpClient.getResponseBufferSize());
|
||||
client.setUseInputDirectByteBuffers(httpClient.isUseInputDirectByteBuffers());
|
||||
client.setUseOutputDirectByteBuffers(httpClient.isUseOutputDirectByteBuffers());
|
||||
client.setConnectBlocking(httpClient.isConnectBlocking());
|
||||
client.setBindAddress(httpClient.getBindAddress());
|
||||
}
|
||||
addBean(client);
|
||||
super.doStart();
|
||||
|
|
|
@ -18,6 +18,7 @@ import java.net.InetSocketAddress;
|
|||
import java.net.ProtocolFamily;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketOption;
|
||||
import java.net.StandardProtocolFamily;
|
||||
import java.net.StandardSocketOptions;
|
||||
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.JavaVersion;
|
||||
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.ssl.SslContextFactory;
|
||||
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.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 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";
|
||||
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)
|
||||
{
|
||||
return new ClientConnector(SocketChannelWithAddress.Factory.forUnixDomain(path));
|
||||
|
@ -65,7 +100,11 @@ public class ClientConnector extends ContainerLifeCycle
|
|||
private Duration connectTimeout = Duration.ofSeconds(5);
|
||||
private Duration idleTimeout = Duration.ofSeconds(30);
|
||||
private SocketAddress bindAddress;
|
||||
private boolean tcpNoDelay = true;
|
||||
private boolean reuseAddress = true;
|
||||
private boolean reusePort;
|
||||
private int receiveBufferSize = -1;
|
||||
private int sendBufferSize = -1;
|
||||
|
||||
public ClientConnector()
|
||||
{
|
||||
|
@ -129,6 +168,10 @@ public class ClientConnector extends ContainerLifeCycle
|
|||
this.sslContextFactory = sslContextFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of NIO selectors
|
||||
*/
|
||||
@ManagedAttribute("The number of NIO selectors")
|
||||
public int getSelectors()
|
||||
{
|
||||
return selectors;
|
||||
|
@ -141,6 +184,10 @@ public class ClientConnector extends ContainerLifeCycle
|
|||
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()
|
||||
{
|
||||
return connectBlocking;
|
||||
|
@ -151,6 +198,10 @@ public class ClientConnector extends ContainerLifeCycle
|
|||
this.connectBlocking = connectBlocking;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the timeout of {@link #connect(SocketAddress, Map)} operations
|
||||
*/
|
||||
@ManagedAttribute("The timeout of connect operations")
|
||||
public Duration getConnectTimeout()
|
||||
{
|
||||
return connectTimeout;
|
||||
|
@ -163,6 +214,10 @@ public class ClientConnector extends ContainerLifeCycle
|
|||
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()
|
||||
{
|
||||
return idleTimeout;
|
||||
|
@ -173,26 +228,120 @@ public class ClientConnector extends ContainerLifeCycle
|
|||
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()
|
||||
{
|
||||
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)
|
||||
{
|
||||
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()
|
||||
{
|
||||
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)
|
||||
{
|
||||
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
|
||||
protected void doStart() throws Exception
|
||||
{
|
||||
|
@ -246,10 +395,12 @@ public class ClientConnector extends ContainerLifeCycle
|
|||
SocketChannelWithAddress channelWithAddress = factory.newSocketChannelWithAddress(address, context);
|
||||
channel = channelWithAddress.getSocketChannel();
|
||||
address = channelWithAddress.getSocketAddress();
|
||||
|
||||
configure(channel);
|
||||
|
||||
SocketAddress bindAddress = getBindAddress();
|
||||
if (bindAddress != null)
|
||||
bind(channel, bindAddress);
|
||||
configure(channel);
|
||||
|
||||
boolean connected = true;
|
||||
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
|
||||
{
|
||||
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);
|
||||
}
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Binding {} to {}", channel, bindAddress);
|
||||
channel.bind(bindAddress);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
channel.setOption(StandardSocketOptions.TCP_NODELAY, true);
|
||||
channel.setOption(option, value);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Could not configure {}", channel);
|
||||
LOG.debug("Could not configure {} to {} on {}", option, value, channel);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
<Set name="acceptorPriorityDelta" property="jetty.http.acceptorPriorityDelta" />
|
||||
<Set name="acceptQueueSize" property="jetty.http.acceptQueueSize" />
|
||||
<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="acceptedReceiveBufferSize" property="jetty.http.acceptedReceiveBufferSize" />
|
||||
<Set name="acceptedSendBufferSize" property="jetty.http.acceptedSendBufferSize" />
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
<Set name="acceptorPriorityDelta" property="jetty.ssl.acceptorPriorityDelta"/>
|
||||
<Set name="acceptQueueSize" property="jetty.ssl.acceptQueueSize"/>
|
||||
<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="acceptedReceiveBufferSize" property="jetty.ssl.acceptedReceiveBufferSize" />
|
||||
<Set name="acceptedSendBufferSize" property="jetty.ssl.acceptedSendBufferSize" />
|
||||
|
|
|
@ -40,6 +40,9 @@ etc/jetty-http.xml
|
|||
## Whether to enable the SO_REUSEADDR socket option.
|
||||
# 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.
|
||||
# jetty.http.acceptedTcpNoDelay=true
|
||||
|
||||
|
|
|
@ -42,6 +42,9 @@ etc/jetty-ssl-context.xml
|
|||
## Whether to enable the SO_REUSEADDR socket option.
|
||||
# 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.
|
||||
# jetty.ssl.acceptedTcpNoDelay=true
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import java.net.InetSocketAddress;
|
|||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.StandardSocketOptions;
|
||||
import java.nio.channels.Channel;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
|
@ -77,6 +78,7 @@ public class ServerConnector extends AbstractNetworkConnector
|
|||
private volatile int _localPort = -1;
|
||||
private volatile int _acceptQueueSize = 0;
|
||||
private volatile boolean _reuseAddress = true;
|
||||
private volatile boolean _reusePort = false;
|
||||
private volatile boolean _acceptedTcpNoDelay = true;
|
||||
private volatile int _acceptedReceiveBufferSize = -1;
|
||||
private volatile int _acceptedSendBufferSize = -1;
|
||||
|
@ -332,8 +334,9 @@ public class ServerConnector extends AbstractNetworkConnector
|
|||
serverChannel = ServerSocketChannel.open();
|
||||
try
|
||||
{
|
||||
serverChannel.socket().setReuseAddress(getReuseAddress());
|
||||
serverChannel.socket().bind(bindAddress, getAcceptQueueSize());
|
||||
serverChannel.setOption(StandardSocketOptions.SO_REUSEADDR, getReuseAddress());
|
||||
serverChannel.setOption(StandardSocketOptions.SO_REUSEPORT, isReusePort());
|
||||
serverChannel.bind(bindAddress, getAcceptQueueSize());
|
||||
}
|
||||
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()
|
||||
*/
|
||||
@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)
|
||||
*/
|
||||
public void setReuseAddress(boolean reuseAddress)
|
||||
|
@ -468,6 +471,23 @@ public class ServerConnector extends AbstractNetworkConnector
|
|||
_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.
|
||||
* @see Socket#getTcpNoDelay()
|
||||
|
|
|
@ -25,6 +25,7 @@ import java.net.Socket;
|
|||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
@ -32,6 +33,9 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
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.SocketChannelEndPoint;
|
||||
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.notNullValue;
|
||||
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.assertThrows;
|
||||
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
|
||||
public void testAddFirstConnectionFactory()
|
||||
{
|
||||
|
|
|
@ -30,6 +30,7 @@ import java.nio.file.Files;
|
|||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jetty.start.Props.Prop;
|
||||
import org.eclipse.jetty.start.config.CommandLineConfigSource;
|
||||
|
@ -467,6 +468,15 @@ public class Main
|
|||
{
|
||||
CommandLineBuilder cmd = args.getMainArgs(StartArgs.ALL_PARTS);
|
||||
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());
|
||||
StartLog.endStartLog();
|
||||
final Process process = pbuilder.start();
|
||||
|
|
|
@ -302,7 +302,7 @@ public class StartArgs
|
|||
// Jetty Environment
|
||||
System.out.println();
|
||||
System.out.println("Jetty Environment:");
|
||||
System.out.println("-----------------");
|
||||
System.out.println("------------------");
|
||||
dumpProperty(JETTY_VERSION_KEY);
|
||||
dumpProperty(JETTY_TAG_NAME_KEY);
|
||||
dumpProperty(JETTY_BUILDNUM_KEY);
|
||||
|
@ -330,26 +330,20 @@ public class StartArgs
|
|||
|
||||
public void dumpJvmArgs()
|
||||
{
|
||||
System.out.println();
|
||||
System.out.println("JVM Arguments:");
|
||||
System.out.println("--------------");
|
||||
if (jvmArgs.isEmpty())
|
||||
{
|
||||
System.out.println(" (no jvm args specified)");
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
System.out.println("Forked JVM Arguments:");
|
||||
System.out.println("---------------------");
|
||||
|
||||
for (String jvmArgKey : jvmArgs)
|
||||
{
|
||||
String value = System.getProperty(jvmArgKey);
|
||||
if (value != null)
|
||||
{
|
||||
System.out.printf(" %s = %s%n", jvmArgKey, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
System.out.printf(" %s%n", jvmArgKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue