Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-11.0.x

This commit is contained in:
Ludovic Orban 2023-08-31 09:30:01 +02:00
commit 475a706fd1
16 changed files with 341 additions and 26 deletions

View File

@ -330,7 +330,7 @@ CYGWIN*) JETTY_STATE="`cygpath -w $JETTY_STATE`";;
esac
JETTY_ARGS=(${JETTY_ARGS[*]} "jetty.state=$JETTY_STATE")
JETTY_ARGS=(${JETTY_ARGS[*]} "jetty.state=$JETTY_STATE" "jetty.pid=$JETTY_PID")
##################################################
# Get the list of config.xml files from jetty.conf
@ -457,6 +457,7 @@ case "$ACTION" in
exit
fi
# Startup from a service file
if [ $UID -eq 0 ] && type start-stop-daemon > /dev/null 2>&1
then
unset CH_USER
@ -482,6 +483,7 @@ case "$ACTION" in
exit 1
fi
# Startup if switching users (not as a service, or from root)
if [ -n "$JETTY_USER" ] && [ `whoami` != "$JETTY_USER" ]
then
unset SU_SHELL
@ -492,16 +494,14 @@ case "$ACTION" in
touch "$JETTY_PID"
chown "$JETTY_USER" "$JETTY_PID"
# FIXME: Broken solution: wordsplitting, pathname expansion, arbitrary command execution, etc.
su - "$JETTY_USER" $SU_SHELL -c "
cd \"$JETTY_BASE\"
echo ${RUN_ARGS[*]} start-log-file=\"$JETTY_START_LOG\" | xargs ${JAVA} > /dev/null &
disown \$!
echo \$! > \"$JETTY_PID\""
disown $(pgrep -P $!)"
else
# Startup if not switching users
echo ${RUN_ARGS[*]} | xargs ${JAVA} > /dev/null &
disown $!
echo $! > "$JETTY_PID"
disown $(pgrep -P $!)
fi
fi
@ -523,6 +523,7 @@ case "$ACTION" in
stop)
echo -n "Stopping Jetty: "
# Stop from a service file
if [ $UID -eq 0 ] && type start-stop-daemon > /dev/null 2>&1; then
start-stop-daemon -K -p"$JETTY_PID" -d"$JETTY_HOME" -a "$JAVA" -s HUP
@ -535,6 +536,7 @@ case "$ACTION" in
sleep 1
done
else
# Stop from a non-service path
if [ ! -f "$JETTY_PID" ] ; then
echo "ERROR: no pid found at $JETTY_PID"
exit 1

View File

@ -1,15 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_9_3.dtd">
<!-- =============================================================== -->
<!-- Mixin the Start FileNoticeLifeCycleListener -->
<!-- =============================================================== -->
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call name="addEventListener">
<Arg>
<New class="org.eclipse.jetty.util.component.FileNoticeLifeCycleListener">
<Arg><Property name="jetty.state" default="./jetty.state"/></Arg>
</New>
</Arg>
</Call>
</Configure>

View File

@ -8,4 +8,4 @@
# Each line in this file becomes an argument to start.jar
# in addition to those found in the start.ini file
# =======================================================
jetty-started.xml
--module=pid,state

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.http.HttpTokens.EndOfContent;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.slf4j.Logger;
@ -249,6 +250,7 @@ public class HttpParser
private ByteBuffer _contentChunk;
private int _length;
private final StringBuilder _string = new StringBuilder();
private long _beginNanoTime = Long.MIN_VALUE;
private static HttpCompliance compliance()
{
@ -300,6 +302,11 @@ public class HttpParser
_complianceListener = (ComplianceViolation.Listener)(_handler instanceof ComplianceViolation.Listener ? _handler : null);
}
public long getBeginNanoTime()
{
return _beginNanoTime;
}
public HttpHandler getHandler()
{
return _handler;
@ -1517,6 +1524,8 @@ public class HttpParser
_methodString = null;
_endOfContent = EndOfContent.UNKNOWN_CONTENT;
_header = null;
if (buffer.hasRemaining())
_beginNanoTime = NanoTime.now();
quickStart(buffer);
}

View File

@ -17,6 +17,8 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.function.Supplier;
import org.eclipse.jetty.util.NanoTime;
public class MetaData implements Iterable<HttpField>
{
/**
@ -115,6 +117,7 @@ public class MetaData implements Iterable<HttpField>
{
private final String _method;
private final HttpURI _uri;
private final long _beginNanoTime;
public Request(HttpFields fields)
{
@ -126,11 +129,19 @@ public class MetaData implements Iterable<HttpField>
this(method, uri, version, fields, Long.MIN_VALUE);
}
public Request(long beginNanoTime, String method, HttpURI uri, HttpVersion version, HttpFields fields)
{
this(beginNanoTime, method, uri, version, fields, Long.MIN_VALUE);
}
public Request(String method, HttpURI uri, HttpVersion version, HttpFields fields, long contentLength)
{
super(version, fields, contentLength);
_method = method;
_uri = uri.asImmutable();
this(method, uri.asImmutable(), version, fields, contentLength, null);
}
public Request(long beginNanoTime, String method, HttpURI uri, HttpVersion version, HttpFields fields, long contentLength)
{
this(beginNanoTime, method, uri.asImmutable(), version, fields, contentLength, null);
}
public Request(String method, String scheme, HostPortHttpField authority, String uri, HttpVersion version, HttpFields fields, long contentLength)
@ -141,10 +152,21 @@ public class MetaData implements Iterable<HttpField>
}
public Request(String method, HttpURI uri, HttpVersion version, HttpFields fields, long contentLength, Supplier<HttpFields> trailers)
{
this(NanoTime.now(), method, uri, version, fields, contentLength, trailers);
}
public Request(long beginNanoTime, String method, HttpURI uri, HttpVersion version, HttpFields fields, long contentLength, Supplier<HttpFields> trailers)
{
super(version, fields, contentLength, trailers);
_method = method;
_uri = uri;
_beginNanoTime = beginNanoTime;
}
public long getBeginNanoTime()
{
return _beginNanoTime;
}
@Override

View File

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call name="addEventListener">
<Arg>
<New class="org.eclipse.jetty.server.StateLifeCycleListener">
<Arg><Property name="jetty.state" default="jetty.state"/></Arg>
</New>
</Arg>
</Call>
</Configure>

View File

@ -0,0 +1,17 @@
# DO NOT EDIT THIS FILE - See: https://eclipse.dev/jetty/documentation/
[description]
Creates and updates state file used by jetty.sh
[tags]
start
[depends]
server
[xml]
etc/jetty-state.xml
[ini-template]
## State file path
# jetty.state=${jetty.base}/jetty.state

View File

@ -36,6 +36,7 @@ import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.util.NanoTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -340,6 +341,7 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
@Override
public boolean headerComplete()
{
_requestBuilder.beginNanoTime(_httpConnection.getBeginNanoTime());
_metadata = _requestBuilder.build();
onRequest(_metadata);
@ -705,6 +707,7 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
private final HttpURI.Mutable _uriBuilder = HttpURI.build();
private String _method;
private HttpVersion _version;
private long _beginNanoTime = Long.MIN_VALUE;
public String method()
{
@ -719,6 +722,13 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
_fieldsBuilder.clear();
}
public void beginNanoTime(long nanoTime)
{
if (nanoTime == Long.MIN_VALUE)
nanoTime++;
_beginNanoTime = nanoTime;
}
public HttpFields.Mutable getFields()
{
return _fieldsBuilder;
@ -726,7 +736,8 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
public MetaData.Request build()
{
return new MetaData.Request(_method, _uriBuilder, _version, _fieldsBuilder);
long nanoTime = _beginNanoTime == Long.MIN_VALUE ? NanoTime.now() : _beginNanoTime;
return new MetaData.Request(nanoTime, _method, _uriBuilder, _version, _fieldsBuilder);
}
public HttpVersion version()

View File

@ -107,6 +107,11 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
LOG.debug("New HTTP Connection {}", this);
}
public long getBeginNanoTime()
{
return _parser.getBeginNanoTime();
}
public HttpConfiguration getHttpConfiguration()
{
return _config;

View File

@ -1706,6 +1706,16 @@ public class Request implements HttpServletRequest
_secure = secure;
}
/**
* <p>Get the nanoTime at which the request arrived to a connector, obtained via {@link System#nanoTime()}.
* This method can be used when measuring latencies.</p>
* @return The nanoTime at which the request was received/created in nanoseconds
*/
public long getBeginNanoTime()
{
return _metaData.getBeginNanoTime();
}
@Override
public boolean isUserInRole(String role)
{

View File

@ -0,0 +1,89 @@
//
// ========================================================================
// 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.server;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.eclipse.jetty.util.component.LifeCycle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.StandardOpenOption.APPEND;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.WRITE;
/**
* A LifeCycle Listener that writes state changes to a file.
* <p>This can be used with the jetty.sh script to wait for successful startup.
*/
public class StateLifeCycleListener implements LifeCycle.Listener
{
private static final Logger LOG = LoggerFactory.getLogger(StateLifeCycleListener.class);
private final Path _filename;
public StateLifeCycleListener(String filename)
{
_filename = Paths.get(filename).toAbsolutePath();
if (LOG.isDebugEnabled())
LOG.debug("State File: {}", _filename);
}
private void writeState(String action, LifeCycle lifecycle)
{
try (Writer out = Files.newBufferedWriter(_filename, UTF_8, WRITE, CREATE, APPEND))
{
out.append(action).append(" ").append(lifecycle.toString()).append("\n");
}
catch (Exception e)
{
LOG.warn("Unable to write state", e);
}
}
@Override
public void lifeCycleStarting(LifeCycle event)
{
writeState("STARTING", event);
}
@Override
public void lifeCycleStarted(LifeCycle event)
{
writeState("STARTED", event);
}
@Override
public void lifeCycleFailure(LifeCycle event, Throwable cause)
{
writeState("FAILED", event);
}
@Override
public void lifeCycleStopping(LifeCycle event)
{
writeState("STOPPING", event);
}
@Override
public void lifeCycleStopped(LifeCycle event)
{
writeState("STOPPED", event);
}
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure>
<Call class="org.eclipse.jetty.util.PidFile" name="create">
<Arg name="file"><Property name="jetty.pid" default="jetty.pid"/></Arg>
</Call>
</Configure>

View File

@ -0,0 +1,19 @@
# DO NOT EDIT THIS FILE - See: https://eclipse.dev/jetty/documentation/
[description]
Creates the PID file for the Jetty process
[tags]
start
[before]
server
threadpool
jvm
[xml]
etc/jetty-pid.xml
[ini-template]
## PID file path
# jetty.pid=${jetty.base}/jetty.pid

View File

@ -0,0 +1,83 @@
//
// ========================================================================
// 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.util;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jetty.util.annotation.Name;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;
/**
* Create a PID file for the running process.
*
* <p>
* Will register itself in a {@link Runtime#addShutdownHook(Thread)}
* to cleanup the PID file it created during normal JVM shutdown.
* </p>
*/
public class PidFile extends Thread
{
private static final Logger LOG = LoggerFactory.getLogger(PidFile.class);
private static final Set<Path> activeFiles = ConcurrentHashMap.newKeySet();
public static void create(@Name("file") String filename) throws IOException
{
Path pidFile = Paths.get(filename).toAbsolutePath();
if (activeFiles.add(pidFile))
{
Runtime.getRuntime().addShutdownHook(new PidFile(pidFile));
if (Files.exists(pidFile))
LOG.info("Overwriting existing PID file: {}", pidFile);
// Create the PID file as soon as possible.
long pid = ProcessHandle.current().pid();
Files.writeString(pidFile, Long.toString(pid), UTF_8, CREATE, WRITE, TRUNCATE_EXISTING);
if (LOG.isDebugEnabled())
LOG.debug("PID file: {}", pidFile);
}
}
private final Path pidFile;
private PidFile(Path pidFile)
{
this.pidFile = pidFile;
}
@Override
public void run()
{
try
{
Files.deleteIfExists(pidFile);
}
catch (Throwable t)
{
LOG.info("Unable to remove PID file: {}", pidFile, t);
}
}
}

View File

@ -22,7 +22,9 @@ import org.slf4j.LoggerFactory;
/**
* A LifeCycle Listener that writes state changes to a file.
* <p>This can be used with the jetty.sh script to wait for successful startup.
* @deprecated use {@code org.eclipse.jetty.server.StateLifeCycleListener} instead
*/
@Deprecated
public class FileNoticeLifeCycleListener implements LifeCycle.Listener
{
private static final Logger LOG = LoggerFactory.getLogger(FileNoticeLifeCycleListener.class);

View File

@ -23,6 +23,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Queue;
@ -64,6 +65,7 @@ import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
@ -107,6 +109,45 @@ public class DistributionTests extends AbstractJettyHomeTest
}
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testPidFile(boolean includeJettyPidConfig) throws Exception
{
String jettyVersion = System.getProperty("jettyVersion");
JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
.jettyVersion(jettyVersion)
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
.build();
try (JettyHomeTester.Run run1 = distribution.start("--add-modules=http,pid"))
{
assertTrue(run1.awaitFor(10, TimeUnit.SECONDS));
assertEquals(0, run1.getExitValue());
Path pidfile = run1.getConfig().getJettyBase().resolve("jetty.pid");
int port = distribution.freePort();
String[] args = new String[includeJettyPidConfig ? 2 : 1];
args[0] = "jetty.http.port=" + port;
if (includeJettyPidConfig)
args[1] = "jetty.pid=" + pidfile;
try (JettyHomeTester.Run run2 = distribution.start(args))
{
assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS));
assertTrue(Files.isRegularFile(pidfile), "PID file should exist");
startHttpClient();
ContentResponse response = client.GET("http://localhost:" + port);
assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
}
await().atMost(Duration.ofSeconds(10)).until(() -> !Files.exists(pidfile));
}
}
@Test
public void testQuickStartGenerationAndRun() throws Exception
{