diff --git a/documentation/jetty/.gitignore b/documentation/jetty/.gitignore
new file mode 100644
index 00000000000..27ef6457e2b
--- /dev/null
+++ b/documentation/jetty/.gitignore
@@ -0,0 +1,2 @@
+/.asciidoctorconfig
+/provided-antora-playbook.yml
diff --git a/documentation/jetty/README.adoc b/documentation/jetty/README.adoc
new file mode 100644
index 00000000000..e6321b51e0e
--- /dev/null
+++ b/documentation/jetty/README.adoc
@@ -0,0 +1,32 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= Jetty Documentation
+
+This project is the root of the Jetty documentation.
+The content files in this project get sourced by the Antora playbook in the playbook repository that builds the website.
+
+In order to build the documentation locally, you first need to prepare a jetty-home directory by running the following command from the top-level folder of the Jetty project:
+
+ $ mvn install -Dcollector -Pfast -am -pl documentation/jetty
+
+Then you can use the following command from this directory to prepare and run Antora using a preview profile:
+
+ $ mvn antora -N
+
+If you don't run the first command, the Antora build will still succeed, but you will get warnings about missing includes for files taken from jetty-home.
+
+The `antora:antora` goal, which the `antora` lifecycle invokes, takes advantage of the playbook provider feature so the playbook for this branch can be centrally managed in the playbook repository.
+
+Note that this preview profile does not run the jetty blocks, so you will only see the configuration for those runs in the preview site.
+If you want to build the full site, use the build in the playbook repository.
diff --git a/documentation/jetty/antora.yml b/documentation/jetty/antora.yml
new file mode 100644
index 00000000000..1a073abaaea
--- /dev/null
+++ b/documentation/jetty/antora.yml
@@ -0,0 +1,25 @@
+name: jetty
+version: '10'
+title: Eclipse Jetty
+asciidoc:
+ attributes:
+ javadoc-url: https://eclipse.dev/jetty/javadoc/jetty-10
+ jdurl: '{javadoc-url}'
+ jetty-home: ${jetty.home}@
+ version: 10.0.21-SNAPSHOT
+ idprefix: ''
+ idseparator: ''
+ run-jetty-classpath: ${settings.localRepository}/org/eclipse/jetty/tests/jetty-home-tester/${project.version}/jetty-home-tester-${project.version}.jar${path.separator}${run.jetty.classpath}
+nav:
+- modules/operations-guide/nav.adoc
+- modules/programming-guide/nav.adoc
+ext:
+ collector:
+ - run:
+ command: mvn install -ntp -B -Dcollector -Pfast -am -pl documentation/jetty
+ scan:
+ dir: documentation/jetty/target/collector
+ - scan:
+ dir: jetty-server/src/main/java
+ files: org/eclipse/jetty/server/CustomRequestLog.java
+ base: modules/code/partials
diff --git a/documentation/jetty/modules/ROOT/pages/index.adoc b/documentation/jetty/modules/ROOT/pages/index.adoc
new file mode 100644
index 00000000000..ac1c57eb289
--- /dev/null
+++ b/documentation/jetty/modules/ROOT/pages/index.adoc
@@ -0,0 +1,24 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= Eclipse Jetty
+
+This section of the site contains the documentation for {page-component-title} {page-version}.
+
+== xref:operations-guide:index.adoc[]
+
+The Eclipse Jetty Operations Guide targets sysops, devops, and developers who want to install Eclipse Jetty as a standalone server to deploy web applications.
+
+== xref:programming-guide:index.adoc[]
+
+The Eclipse Jetty Programming Guide targets developers who want to use the Eclipse Jetty libraries in their applications, and advanced sysops/devops that want to customize the deployment of web applications.
diff --git a/documentation/jetty/modules/code/examples/jetty-modules/jpms.mod b/documentation/jetty/modules/code/examples/jetty-modules/jpms.mod
new file mode 100644
index 00000000000..6d261a6eb54
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/jetty-modules/jpms.mod
@@ -0,0 +1,8 @@
+[description]
+JPMS Configuration Module
+
+[ini]
+--jpms
+
+[jpms]
+# Additional JPMS configuration.
diff --git a/documentation/jetty/modules/code/examples/jetty-modules/jvm.mod b/documentation/jetty/modules/code/examples/jetty-modules/jvm.mod
new file mode 100644
index 00000000000..e8b2fc51d0a
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/jetty-modules/jvm.mod
@@ -0,0 +1,6 @@
+[description]
+JVM Options Module
+
+[exec]
+-Xmx1g
+-Xlog:gc*,gc+stats=off:file=logs/gc.log:time,level,tags
diff --git a/documentation/jetty/modules/code/examples/jetty-modules/postgresql.mod b/documentation/jetty/modules/code/examples/jetty-modules/postgresql.mod
new file mode 100644
index 00000000000..aadffda2ca9
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/jetty-modules/postgresql.mod
@@ -0,0 +1,15 @@
+[description]
+Postgres JDBC Driver Module
+
+[lib]
+lib/postgresql-${postgresql-version}.jar
+
+[files]
+maven://org.postgresql/postgresql/${postgresql-version}|lib/postgresql-${postgresql-version}.jar
+
+[ini]
+postgresql-version?=42.2.18
+
+[ini-template]
+## Postgres JDBC version.
+# postgresql-version=42.2.18
diff --git a/documentation/jetty/modules/code/examples/jetty-modules/remote-debug.mod b/documentation/jetty/modules/code/examples/jetty-modules/remote-debug.mod
new file mode 100644
index 00000000000..9cae360e33f
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/jetty-modules/remote-debug.mod
@@ -0,0 +1,5 @@
+[description]
+Enables remote debugging
+
+[exec]
+-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
diff --git a/documentation/jetty/modules/code/examples/pom.xml b/documentation/jetty/modules/code/examples/pom.xml
new file mode 100644
index 00000000000..3f920805697
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/pom.xml
@@ -0,0 +1,203 @@
+
+
+
+ 4.0.0
+
+ org.eclipse.jetty.documentation
+ documentation-parent
+ 10.0.21-SNAPSHOT
+ ../../../../pom.xml
+
+ code-examples
+ pom
+ Documentation :: Code Examples
+
+
+ true
+ true
+
+
+
+
+ org.eclipse.jetty
+ infinispan-embedded-query
+
+
+ org.eclipse.jetty
+ infinispan-remote-query
+
+
+ org.eclipse.jetty
+ jetty-alpn-server
+
+
+ org.eclipse.jetty
+ jetty-client
+
+
+ org.eclipse.jetty
+ jetty-jmx
+
+
+ org.eclipse.jetty
+ jetty-nosql
+
+
+ org.eclipse.jetty
+ jetty-rewrite
+
+
+ org.eclipse.jetty
+ jetty-server
+
+
+ org.eclipse.jetty
+ jetty-servlets
+
+
+ org.eclipse.jetty
+ jetty-unixdomain-server
+
+
+ org.eclipse.jetty
+ jetty-util-ajax
+
+
+ org.eclipse.jetty.fcgi
+ fcgi-client
+
+
+ org.eclipse.jetty.gcloud
+ jetty-gcloud-session-manager
+
+
+ org.eclipse.jetty.http2
+ http2-http-client-transport
+
+
+ org.eclipse.jetty.http2
+ http2-server
+
+
+ org.eclipse.jetty.http3
+ http3-http-client-transport
+
+
+ org.eclipse.jetty.http3
+ http3-server
+
+
+ org.eclipse.jetty.memcached
+ jetty-memcached-sessions
+
+
+
+ org.eclipse.jetty.websocket
+ websocket-javax-server
+
+
+ org.eclipse.jetty.websocket
+ websocket-jetty-client
+
+
+ org.eclipse.jetty.websocket
+ websocket-jetty-server
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ compile-code-examples
+
+ compile
+
+ compile
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+
+ false
+
+
+ [21,)
+ [ERROR] OLD JDK [${java.version}] in use. Jetty documentation ${project.version} MUST use JDK 21 or newer
+
+
+
+
+
+
+
+
+
+ jdk17-18
+
+ [17,19)
+
+
+
+
+ maven-compiler-plugin
+
+
+ **/ArchitectureDocs.java
+
+
+
+
+
+
+
+ jdk19-20
+
+ [19,21)
+
+
+
+
+ maven-compiler-plugin
+
+ ${java.specification.version}
+ ${java.specification.version}
+ ${java.specification.version}
+ true
+
+
+
+
+
+
+ jdk21+
+
+ [21,)
+
+
+
+
+ maven-compiler-plugin
+
+ ${java.specification.version}
+ ${java.specification.version}
+ ${java.specification.version}
+
+
+
+
+
+
+
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/ComponentDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/ComponentDocs.java
new file mode 100644
index 00000000000..f309dad3af3
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/ComponentDocs.java
@@ -0,0 +1,284 @@
+//
+// ========================================================================
+// 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.docs.programming;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.Container;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+import static java.lang.System.Logger.Level.INFO;
+
+@SuppressWarnings("unused")
+public class ComponentDocs
+{
+ public void start() throws Exception
+ {
+ // tag::start[]
+ class Monitor extends AbstractLifeCycle
+ {
+ }
+
+ class Root extends ContainerLifeCycle
+ {
+ // Monitor is an internal component.
+ private final Monitor monitor = new Monitor();
+
+ public Root()
+ {
+ // The Monitor life cycle is managed by Root.
+ addManaged(monitor);
+ }
+ }
+
+ class Service extends ContainerLifeCycle
+ {
+ // An instance of the Java scheduler service.
+ private ScheduledExecutorService scheduler;
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ // Java's schedulers cannot be restarted, so they must
+ // be created anew every time their container is started.
+ scheduler = Executors.newSingleThreadScheduledExecutor();
+ // Even if Java scheduler does not implement
+ // LifeCycle, make it part of the component tree.
+ addBean(scheduler);
+ // Start all the children beans.
+ super.doStart();
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ // Perform the opposite operations that were
+ // performed in doStart(), in reverse order.
+ super.doStop();
+ removeBean(scheduler);
+ scheduler.shutdown();
+ }
+ }
+
+ // Create a Root instance.
+ Root root = new Root();
+
+ // Create a Service instance.
+ Service service = new Service();
+
+ // Link the components.
+ root.addBean(service);
+
+ // Start the root component to
+ // start the whole component tree.
+ root.start();
+ // end::start[]
+ }
+
+ public void restart() throws Exception
+ {
+ // tag::restart[]
+ class Root extends ContainerLifeCycle
+ {
+ }
+
+ class Service extends ContainerLifeCycle
+ {
+ // An instance of the Java scheduler service.
+ private ScheduledExecutorService scheduler;
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ // Java's schedulers cannot be restarted, so they must
+ // be created anew every time their container is started.
+ scheduler = Executors.newSingleThreadScheduledExecutor();
+ // Even if Java scheduler does not implement
+ // LifeCycle, make it part of the component tree.
+ addBean(scheduler);
+ // Start all the children beans.
+ super.doStart();
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ // Perform the opposite operations that were
+ // performed in doStart(), in reverse order.
+ super.doStop();
+ removeBean(scheduler);
+ scheduler.shutdown();
+ }
+ }
+
+ Root root = new Root();
+ Service service = new Service();
+ root.addBean(service);
+
+ // Start the Root component.
+ root.start();
+
+ // Stop temporarily Service without stopping the Root.
+ service.stop();
+
+ // Restart Service.
+ service.start();
+ // end::restart[]
+ }
+
+ public void getBeans() throws Exception
+ {
+ // tag::getBeans[]
+ class Root extends ContainerLifeCycle
+ {
+ }
+
+ class Service extends ContainerLifeCycle
+ {
+ private ScheduledExecutorService scheduler;
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ scheduler = Executors.newSingleThreadScheduledExecutor();
+ addBean(scheduler);
+ super.doStart();
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ removeBean(scheduler);
+ scheduler.shutdown();
+ }
+ }
+
+ Root root = new Root();
+ Service service = new Service();
+ root.addBean(service);
+
+ // Start the Root component.
+ root.start();
+
+ // Find all the direct children of root.
+ Collection children = root.getBeans();
+ // children contains only service
+
+ // Find all descendants of root that are instance of a particular class.
+ Collection schedulers = root.getContainedBeans(ScheduledExecutorService.class);
+ // schedulers contains the service scheduler.
+ // end::getBeans[]
+ }
+
+ public void lifecycleListener()
+ {
+ // tag::lifecycleListener[]
+ Server server = new Server();
+
+ // Add an event listener of type LifeCycle.Listener.
+ server.addEventListener(new LifeCycle.Listener()
+ {
+ @Override
+ public void lifeCycleStarted(LifeCycle lifeCycle)
+ {
+ System.getLogger("server").log(INFO, "Server {0} has been started", lifeCycle);
+ }
+
+ @Override
+ public void lifeCycleFailure(LifeCycle lifeCycle, Throwable failure)
+ {
+ System.getLogger("server").log(INFO, "Server {0} failed to start", lifeCycle, failure);
+ }
+
+ @Override
+ public void lifeCycleStopped(LifeCycle lifeCycle)
+ {
+ System.getLogger("server").log(INFO, "Server {0} has been stopped", lifeCycle);
+ }
+ });
+ // end::lifecycleListener[]
+ }
+
+ public void containerListener()
+ {
+ // tag::containerListener[]
+ Server server = new Server();
+
+ // Add an event listener of type LifeCycle.Listener.
+ server.addEventListener(new Container.Listener()
+ {
+ @Override
+ public void beanAdded(Container parent, Object child)
+ {
+ System.getLogger("server").log(INFO, "Added bean {1} to {0}", parent, child);
+ }
+
+ @Override
+ public void beanRemoved(Container parent, Object child)
+ {
+ System.getLogger("server").log(INFO, "Removed bean {1} from {0}", parent, child);
+ }
+ });
+ // end::containerListener[]
+ }
+
+ public void containerSiblings()
+ {
+ // tag::containerSiblings[]
+ class Parent extends ContainerLifeCycle
+ {
+ }
+
+ class Child
+ {
+ }
+
+ // The older child takes care of its siblings.
+ class OlderChild extends Child implements Container.Listener
+ {
+ private Set siblings = new HashSet<>();
+
+ @Override
+ public void beanAdded(Container parent, Object child)
+ {
+ siblings.add(child);
+ }
+
+ @Override
+ public void beanRemoved(Container parent, Object child)
+ {
+ siblings.remove(child);
+ }
+ }
+
+ Parent parent = new Parent();
+
+ Child older = new OlderChild();
+ // The older child is a child bean _and_ a listener.
+ parent.addBean(older);
+
+ Child younger = new Child();
+ // Adding a younger child will notify the older child.
+ parent.addBean(younger);
+ // end::containerSiblings[]
+ }
+}
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/HTTP2Docs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/HTTP2Docs.java
new file mode 100644
index 00000000000..5bc4f08d176
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/HTTP2Docs.java
@@ -0,0 +1,92 @@
+//
+// ========================================================================
+// 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.docs.programming;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MetaData;
+import org.eclipse.jetty.http2.api.Session;
+import org.eclipse.jetty.http2.api.Stream;
+import org.eclipse.jetty.http2.client.HTTP2Client;
+import org.eclipse.jetty.http2.frames.DataFrame;
+import org.eclipse.jetty.http2.frames.HeadersFrame;
+import org.eclipse.jetty.util.Callback;
+
+@SuppressWarnings("unused")
+public class HTTP2Docs
+{
+ public void dataDemanded() throws Exception
+ {
+ HTTP2Client http2Client = new HTTP2Client();
+ http2Client.start();
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
+ CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
+ Session session = sessionCF.get();
+
+ HttpFields requestHeaders = HttpFields.build()
+ .put(HttpHeader.USER_AGENT, "Jetty HTTP2Client {version}");
+ MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders);
+ HeadersFrame headersFrame = new HeadersFrame(request, null, true);
+
+ // tag::dataDemanded[]
+ class Chunk
+ {
+ private final ByteBuffer buffer;
+ private final Callback callback;
+
+ Chunk(ByteBuffer buffer, Callback callback)
+ {
+ this.buffer = buffer;
+ this.callback = callback;
+ }
+ }
+
+ // A queue that consumers poll to consume content asynchronously.
+ Queue dataQueue = new ConcurrentLinkedQueue<>();
+
+ // Implementation of Stream.Listener.onDataDemanded(...)
+ // in case of asynchronous content consumption and demand.
+ Stream.Listener listener = new Stream.Listener.Adapter()
+ {
+ @Override
+ public void onDataDemanded(Stream stream, DataFrame frame, Callback callback)
+ {
+ // Get the content buffer.
+ ByteBuffer buffer = frame.getData();
+
+ // Store buffer to consume it asynchronously, and wrap the callback.
+ dataQueue.offer(new Chunk(buffer, Callback.from(() ->
+ {
+ // When the buffer has been consumed, then:
+ // A) succeed the nested callback.
+ callback.succeeded();
+ // B) demand more DATA frames.
+ stream.demand(1);
+ }, callback::failed)));
+
+ // Do not demand more content here, to avoid to overflow the queue.
+ }
+ };
+ // end::dataDemanded[]
+ }
+}
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/JMXDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/JMXDocs.java
new file mode 100644
index 00000000000..4ffdee23642
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/JMXDocs.java
@@ -0,0 +1,271 @@
+//
+// ========================================================================
+// 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.docs.programming;
+
+import java.lang.management.ManagementFactory;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+import javax.rmi.ssl.SslRMIClientSocketFactory;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.jmx.ConnectorServer;
+import org.eclipse.jetty.jmx.MBeanContainer;
+import org.eclipse.jetty.jmx.ObjectMBean;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+@SuppressWarnings("unused")
+public class JMXDocs
+{
+ public void server()
+ {
+ // tag::server[]
+ Server server = new Server();
+
+ // Create an MBeanContainer with the platform MBeanServer.
+ MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
+
+ // Add MBeanContainer to the root component.
+ server.addBean(mbeanContainer);
+ // end::server[]
+ }
+
+ public void client()
+ {
+ // tag::client[]
+ HttpClient httpClient = new HttpClient();
+
+ // Create an MBeanContainer with the platform MBeanServer.
+ MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
+
+ // Add MBeanContainer to the root component.
+ httpClient.addBean(mbeanContainer);
+ // end::client[]
+ }
+
+ public void remote() throws Exception
+ {
+ // tag::remote[]
+ Server server = new Server();
+
+ // Setup Jetty JMX.
+ MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
+ server.addBean(mbeanContainer);
+
+ // Setup ConnectorServer.
+
+ // Bind the RMI server to the wildcard address and port 1999.
+ // Bind the RMI registry to the wildcard address and port 1099.
+ JMXServiceURL jmxURL = new JMXServiceURL("rmi", null, 1999, "/jndi/rmi:///jmxrmi");
+ ConnectorServer jmxServer = new ConnectorServer(jmxURL, "org.eclipse.jetty.jmx:name=rmiconnectorserver");
+
+ // Add ConnectorServer as a bean, so it is started
+ // with the Server and also exported as MBean.
+ server.addBean(jmxServer);
+
+ server.start();
+ // end::remote[]
+ }
+
+ public static void main(String[] args) throws Exception
+ {
+ new JMXDocs().remote();
+ }
+
+ public void remoteAuthorization() throws Exception
+ {
+ // tag::remoteAuthorization[]
+ Server server = new Server();
+
+ // Setup Jetty JMX.
+ MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
+ server.addBean(mbeanContainer);
+
+ // Setup ConnectorServer.
+ JMXServiceURL jmxURL = new JMXServiceURL("rmi", null, 1099, "/jndi/rmi:///jmxrmi");
+ Map env = new HashMap<>();
+ env.put("com.sun.management.jmxremote.access.file", "/path/to/users.access");
+ env.put("com.sun.management.jmxremote.password.file", "/path/to/users.password");
+ ConnectorServer jmxServer = new ConnectorServer(jmxURL, env, "org.eclipse.jetty.jmx:name=rmiconnectorserver");
+ server.addBean(jmxServer);
+
+ server.start();
+ // end::remoteAuthorization[]
+ }
+
+ public void tlsRemote() throws Exception
+ {
+ // tag::tlsRemote[]
+ Server server = new Server();
+
+ // Setup Jetty JMX.
+ MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
+ server.addBean(mbeanContainer);
+
+ // Setup SslContextFactory.
+ SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+ sslContextFactory.setKeyStorePath("/path/to/keystore");
+ sslContextFactory.setKeyStorePassword("secret");
+
+ // Setup ConnectorServer with SslContextFactory.
+ JMXServiceURL jmxURL = new JMXServiceURL("rmi", null, 1099, "/jndi/rmi:///jmxrmi");
+ ConnectorServer jmxServer = new ConnectorServer(jmxURL, null, "org.eclipse.jetty.jmx:name=rmiconnectorserver", sslContextFactory);
+ server.addBean(jmxServer);
+
+ server.start();
+ // end::tlsRemote[]
+ }
+
+ public void tlsJMXConnector() throws Exception
+ {
+ // tag::tlsJMXConnector[]
+ // System properties necessary for an RMI client to trust a self-signed certificate.
+ System.setProperty("javax.net.ssl.trustStore", "/path/to/trustStore");
+ System.setProperty("javax.net.ssl.trustStorePassword", "secret");
+
+ JMXServiceURL jmxURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://domain.com:1100/jmxrmi");
+
+ Map clientEnv = new HashMap<>();
+ // Required to connect to the RMI registry via TLS.
+ clientEnv.put(ConnectorServer.RMI_REGISTRY_CLIENT_SOCKET_FACTORY_ATTRIBUTE, new SslRMIClientSocketFactory());
+
+ try (JMXConnector client = JMXConnectorFactory.connect(jmxURL, clientEnv))
+ {
+ Set names = client.getMBeanServerConnection().queryNames(null, null);
+ }
+ // end::tlsJMXConnector[]
+ }
+
+ public void jmxAnnotation() throws Exception
+ {
+ // tag::jmxAnnotation[]
+ // Annotate the class with @ManagedObject and provide a description.
+ @ManagedObject("Services that provide useful features")
+ class Services
+ {
+ private final Map services = new ConcurrentHashMap<>();
+ private boolean enabled = true;
+
+ // A read-only attribute with description.
+ @ManagedAttribute(value = "The number of services", readonly = true)
+ public int getServiceCount()
+ {
+ return services.size();
+ }
+
+ // A read-write attribute with description.
+ // Only the getter is annotated.
+ @ManagedAttribute(value = "Whether the services are enabled")
+ public boolean isEnabled()
+ {
+ return enabled;
+ }
+
+ // There is no need to annotate the setter.
+ public void setEnabled(boolean enabled)
+ {
+ this.enabled = enabled;
+ }
+
+ // An operation with description and impact.
+ // The @Name annotation is used to annotate parameters
+ // for example to display meaningful parameter names.
+ @ManagedOperation(value = "Retrieves the service with the given name", impact = "INFO")
+ public Object getService(@Name(value = "serviceName") String n)
+ {
+ return services.get(n);
+ }
+ }
+ // end::jmxAnnotation[]
+ }
+
+ public void jmxCustomMBean()
+ {
+ // tag::jmxCustomMBean[]
+ //package com.acme;
+ @ManagedObject
+ class Service
+ {
+ }
+
+ //package com.acme.jmx;
+ class ServiceMBean extends ObjectMBean
+ {
+ ServiceMBean(Object service)
+ {
+ super(service);
+ }
+ }
+ // end::jmxCustomMBean[]
+ }
+
+ public void jmxCustomMBeanOverride()
+ {
+ // tag::jmxCustomMBeanOverride[]
+ //package com.acme;
+ // No Jetty JMX annotations.
+ class CountService
+ {
+ private int count;
+
+ public int getCount()
+ {
+ return count;
+ }
+
+ public void addCount(int value)
+ {
+ count += value;
+ }
+ }
+
+ //package com.acme.jmx;
+ @ManagedObject("the count service")
+ class CountServiceMBean extends ObjectMBean
+ {
+ public CountServiceMBean(Object service)
+ {
+ super(service);
+ }
+
+ private CountService getCountService()
+ {
+ return (CountService)super.getManagedObject();
+ }
+
+ @ManagedAttribute("the current service count")
+ public int getCount()
+ {
+ return getCountService().getCount();
+ }
+
+ @ManagedOperation(value = "adds the given value to the service count", impact = "ACTION")
+ public void addCount(@Name("count delta") int value)
+ {
+ getCountService().addCount(value);
+ }
+ }
+ // end::jmxCustomMBeanOverride[]
+ }
+}
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/SelectorManagerDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/SelectorManagerDocs.java
new file mode 100644
index 00000000000..74ced90a888
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/SelectorManagerDocs.java
@@ -0,0 +1,253 @@
+//
+// ========================================================================
+// 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.docs.programming;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingCallback;
+
+@SuppressWarnings("unused")
+public class SelectorManagerDocs
+{
+ // tag::connect[]
+ public void connect(SelectorManager selectorManager, Map context) throws IOException
+ {
+ String host = "host";
+ int port = 8080;
+
+ // Create an unconnected SocketChannel.
+ SocketChannel socketChannel = SocketChannel.open();
+ socketChannel.configureBlocking(false);
+
+ // Connect and register to Jetty.
+ if (socketChannel.connect(new InetSocketAddress(host, port)))
+ selectorManager.accept(socketChannel, context);
+ else
+ selectorManager.connect(socketChannel, context);
+ }
+ // end::connect[]
+
+ // tag::accept[]
+ public void accept(ServerSocketChannel acceptor, SelectorManager selectorManager) throws IOException
+ {
+ // Wait until a client connects.
+ SocketChannel socketChannel = acceptor.accept();
+ socketChannel.configureBlocking(false);
+
+ // Accept and register to Jetty.
+ Object attachment = null;
+ selectorManager.accept(socketChannel, attachment);
+ }
+ // end::accept[]
+
+ public void connection()
+ {
+ // tag::connection[]
+ // Extend AbstractConnection to inherit basic implementation.
+ class MyConnection extends AbstractConnection
+ {
+ public MyConnection(EndPoint endPoint, Executor executor)
+ {
+ super(endPoint, executor);
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+
+ // Declare interest for fill events.
+ fillInterested();
+ }
+
+ @Override
+ public void onFillable()
+ {
+ // Called when a fill event happens.
+ }
+ }
+ // end::connection[]
+ }
+
+ public void echoWrong()
+ {
+ // tag::echo-wrong[]
+ class WrongEchoConnection extends AbstractConnection implements Callback
+ {
+ public WrongEchoConnection(EndPoint endPoint, Executor executor)
+ {
+ super(endPoint, executor);
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+
+ // Declare interest for fill events.
+ fillInterested();
+ }
+
+ @Override
+ public void onFillable()
+ {
+ try
+ {
+ ByteBuffer buffer = BufferUtil.allocate(1024);
+ int filled = getEndPoint().fill(buffer);
+ if (filled > 0)
+ {
+ // Filled some bytes, echo them back.
+ getEndPoint().write(this, buffer);
+ }
+ else if (filled == 0)
+ {
+ // No more bytes to fill, declare
+ // again interest for fill events.
+ fillInterested();
+ }
+ else
+ {
+ // The other peer closed the
+ // connection, close it back.
+ getEndPoint().close();
+ }
+ }
+ catch (Exception x)
+ {
+ getEndPoint().close(x);
+ }
+ }
+
+ @Override
+ public void succeeded()
+ {
+ // The write is complete, fill again.
+ onFillable();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ getEndPoint().close(x);
+ }
+ }
+ // end::echo-wrong[]
+ }
+
+ public void echoCorrect()
+ {
+ // tag::echo-correct[]
+ class EchoConnection extends AbstractConnection
+ {
+ private final IteratingCallback callback = new EchoIteratingCallback();
+
+ public EchoConnection(EndPoint endp, Executor executor)
+ {
+ super(endp, executor);
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+
+ // Declare interest for fill events.
+ fillInterested();
+ }
+
+ @Override
+ public void onFillable()
+ {
+ // Start the iteration loop that reads and echoes back.
+ callback.iterate();
+ }
+
+ class EchoIteratingCallback extends IteratingCallback
+ {
+ private ByteBuffer buffer;
+
+ @Override
+ protected Action process() throws Throwable
+ {
+ // Obtain a buffer if we don't already have one.
+ if (buffer == null)
+ buffer = BufferUtil.allocate(1024);
+
+ int filled = getEndPoint().fill(buffer);
+ if (filled > 0)
+ {
+ // We have filled some bytes, echo them back.
+ getEndPoint().write(this, buffer);
+
+ // Signal that the iteration should resume
+ // when the write() operation is completed.
+ return Action.SCHEDULED;
+ }
+ else if (filled == 0)
+ {
+ // We don't need the buffer anymore, so
+ // don't keep it around while we are idle.
+ buffer = null;
+
+ // No more bytes to read, declare
+ // again interest for fill events.
+ fillInterested();
+
+ // Signal that the iteration is now IDLE.
+ return Action.IDLE;
+ }
+ else
+ {
+ // The other peer closed the connection,
+ // the iteration completed successfully.
+ return Action.SUCCEEDED;
+ }
+ }
+
+ @Override
+ protected void onCompleteSuccess()
+ {
+ // The iteration completed successfully.
+ getEndPoint().close();
+ }
+
+ @Override
+ protected void onCompleteFailure(Throwable cause)
+ {
+ // The iteration completed with a failure.
+ getEndPoint().close(cause);
+ }
+
+ @Override
+ public InvocationType getInvocationType()
+ {
+ return InvocationType.NON_BLOCKING;
+ }
+ }
+ }
+ // end::echo-correct[]
+ }
+}
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/WebSocketDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/WebSocketDocs.java
new file mode 100644
index 00000000000..9433a1d4919
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/WebSocketDocs.java
@@ -0,0 +1,492 @@
+//
+// ========================================================================
+// 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.docs.programming;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+import java.nio.file.Path;
+import java.time.Duration;
+
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.NanoTime;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketListener;
+import org.eclipse.jetty.websocket.api.WebSocketPartialListener;
+import org.eclipse.jetty.websocket.api.WebSocketPingPongListener;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+@SuppressWarnings("unused")
+public class WebSocketDocs
+{
+ @SuppressWarnings("InnerClassMayBeStatic")
+ // tag::listenerEndpoint[]
+ public class ListenerEndPoint implements WebSocketListener // <1>
+ {
+ private Session session;
+
+ @Override
+ public void onWebSocketConnect(Session session)
+ {
+ // The WebSocket connection is established.
+
+ // Store the session to be able to send data to the remote peer.
+ this.session = session;
+
+ // You may configure the session.
+ session.setMaxTextMessageSize(16 * 1024);
+
+ // You may immediately send a message to the remote peer.
+ session.getRemote().sendString("connected", WriteCallback.NOOP);
+ }
+
+ @Override
+ public void onWebSocketClose(int statusCode, String reason)
+ {
+ // The WebSocket connection is closed.
+
+ // You may dispose resources.
+ disposeResources();
+ }
+
+ @Override
+ public void onWebSocketError(Throwable cause)
+ {
+ // The WebSocket connection failed.
+
+ // You may log the error.
+ cause.printStackTrace();
+
+ // You may dispose resources.
+ disposeResources();
+ }
+
+ @Override
+ public void onWebSocketText(String message)
+ {
+ // A WebSocket textual message is received.
+
+ // You may echo it back if it matches certain criteria.
+ if (message.startsWith("echo:"))
+ session.getRemote().sendString(message.substring("echo:".length()), WriteCallback.NOOP);
+ }
+
+ @Override
+ public void onWebSocketBinary(byte[] payload, int offset, int length)
+ {
+ // A WebSocket binary message is received.
+
+ // Save only PNG images.
+ byte[] pngBytes = new byte[]{(byte)0x89, 'P', 'N', 'G'};
+ for (int i = 0; i < pngBytes.length; ++i)
+ {
+ if (pngBytes[i] != payload[offset + i])
+ return;
+ }
+ savePNGImage(payload, offset, length);
+ }
+ }
+ // end::listenerEndpoint[]
+
+ @SuppressWarnings("InnerClassMayBeStatic")
+ // tag::streamingListenerEndpoint[]
+ public class StreamingListenerEndpoint implements WebSocketPartialListener
+ {
+ private Path textPath;
+
+ @Override
+ public void onWebSocketPartialText(String payload, boolean fin)
+ {
+ // Forward chunks to external REST service.
+ forwardToREST(payload, fin);
+ }
+
+ @Override
+ public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin)
+ {
+ // Save chunks to file.
+ appendToFile(payload, fin);
+ }
+ }
+ // end::streamingListenerEndpoint[]
+
+ @SuppressWarnings("InnerClassMayBeStatic")
+ // tag::annotatedEndpoint[]
+ @WebSocket // <1>
+ public class AnnotatedEndPoint
+ {
+ private Session session;
+
+ @OnWebSocketConnect // <2>
+ public void onConnect(Session session)
+ {
+ // The WebSocket connection is established.
+
+ // Store the session to be able to send data to the remote peer.
+ this.session = session;
+
+ // You may configure the session.
+ session.setMaxTextMessageSize(16 * 1024);
+
+ // You may immediately send a message to the remote peer.
+ session.getRemote().sendString("connected", WriteCallback.NOOP);
+ }
+
+ @OnWebSocketClose // <3>
+ public void onClose(int statusCode, String reason)
+ {
+ // The WebSocket connection is closed.
+
+ // You may dispose resources.
+ disposeResources();
+ }
+
+ @OnWebSocketError // <4>
+ public void onError(Throwable cause)
+ {
+ // The WebSocket connection failed.
+
+ // You may log the error.
+ cause.printStackTrace();
+
+ // You may dispose resources.
+ disposeResources();
+ }
+
+ @OnWebSocketMessage // <5>
+ public void onTextMessage(Session session, String message) // <3>
+ {
+ // A WebSocket textual message is received.
+
+ // You may echo it back if it matches certain criteria.
+ if (message.startsWith("echo:"))
+ session.getRemote().sendString(message.substring("echo:".length()), WriteCallback.NOOP);
+ }
+
+ @OnWebSocketMessage // <5>
+ public void onBinaryMessage(byte[] payload, int offset, int length)
+ {
+ // A WebSocket binary message is received.
+
+ // Save only PNG images.
+ byte[] pngBytes = new byte[]{(byte)0x89, 'P', 'N', 'G'};
+ for (int i = 0; i < pngBytes.length; ++i)
+ {
+ if (pngBytes[i] != payload[offset + i])
+ return;
+ }
+ savePNGImage(payload, offset, length);
+ }
+ }
+ // end::annotatedEndpoint[]
+
+ @SuppressWarnings("InnerClassMayBeStatic")
+ // tag::streamingAnnotatedEndpoint[]
+ @WebSocket
+ public class StreamingAnnotatedEndpoint
+ {
+ @OnWebSocketMessage
+ public void onTextMessage(Reader reader)
+ {
+ // Read chunks and forward.
+ forwardToREST(reader);
+ }
+
+ @OnWebSocketMessage
+ public void onBinaryMessage(InputStream stream)
+ {
+ // Save chunks to file.
+ appendToFile(stream);
+ }
+ }
+ // end::streamingAnnotatedEndpoint[]
+
+ @SuppressWarnings("InnerClassMayBeStatic")
+ // tag::sessionConfigure[]
+ public class ConfigureEndpoint implements WebSocketListener
+ {
+ @Override
+ public void onWebSocketConnect(Session session)
+ {
+ // Configure the max length of incoming messages.
+ session.setMaxTextMessageSize(16 * 1024);
+
+ // Configure the idle timeout.
+ session.setIdleTimeout(Duration.ofSeconds(30));
+ }
+ }
+ // end::sessionConfigure[]
+
+ @SuppressWarnings("InnerClassMayBeStatic")
+ // tag::sendBlocking[]
+ @WebSocket
+ public class BlockingSendEndpoint
+ {
+ @OnWebSocketMessage
+ public void onText(Session session, String text)
+ {
+ // Obtain the RemoteEndpoint APIs.
+ RemoteEndpoint remote = session.getRemote();
+
+ try
+ {
+ // Send textual data to the remote peer.
+ remote.sendString("data");
+
+ // Send binary data to the remote peer.
+ ByteBuffer bytes = readImageFromFile();
+ remote.sendBytes(bytes);
+
+ // Send a PING frame to the remote peer.
+ remote.sendPing(ByteBuffer.allocate(8).putLong(NanoTime.now()).flip());
+ }
+ catch (IOException x)
+ {
+ // No need to rethrow or close the session.
+ System.getLogger("websocket").log(System.Logger.Level.WARNING, "could not send data", x);
+ }
+ }
+ }
+ // end::sendBlocking[]
+
+ @SuppressWarnings("InnerClassMayBeStatic")
+ // tag::sendNonBlocking[]
+ @WebSocket
+ public class NonBlockingSendEndpoint
+ {
+ @OnWebSocketMessage
+ public void onText(Session session, String text)
+ {
+ // Obtain the RemoteEndpoint APIs.
+ RemoteEndpoint remote = session.getRemote();
+
+ // Send textual data to the remote peer.
+ remote.sendString("data", new WriteCallback() // <1>
+ {
+ @Override
+ public void writeSuccess()
+ {
+ // Send binary data to the remote peer.
+ ByteBuffer bytes = readImageFromFile();
+ remote.sendBytes(bytes, new WriteCallback() // <2>
+ {
+ @Override
+ public void writeSuccess()
+ {
+ // Both sends succeeded.
+ }
+
+ @Override
+ public void writeFailed(Throwable x)
+ {
+ System.getLogger("websocket").log(System.Logger.Level.WARNING, "could not send binary data", x);
+ }
+ });
+ }
+
+ @Override
+ public void writeFailed(Throwable x)
+ {
+ // No need to rethrow or close the session.
+ System.getLogger("websocket").log(System.Logger.Level.WARNING, "could not send textual data", x);
+ }
+ });
+
+ // remote.sendString("wrong", WriteCallback.NOOP); // May throw WritePendingException! <3>
+ }
+ }
+ // end::sendNonBlocking[]
+
+ @SuppressWarnings("InnerClassMayBeStatic")
+ // tag::streamSendBlocking[]
+ @WebSocket
+ public class StreamSendBlockingEndpoint
+ {
+ @OnWebSocketMessage
+ public void onText(Session session, String text)
+ {
+ try
+ {
+ RemoteEndpoint remote = session.getRemote();
+ while (true)
+ {
+ ByteBuffer chunk = readChunkToSend();
+ if (chunk == null)
+ {
+ // No more bytes, finish the WebSocket message.
+ remote.sendPartialBytes(ByteBuffer.allocate(0), true);
+ break;
+ }
+ else
+ {
+ // Send the chunk.
+ remote.sendPartialBytes(chunk, false);
+ }
+ }
+ }
+ catch (IOException x)
+ {
+ x.printStackTrace();
+ }
+ }
+ }
+ // end::streamSendBlocking[]
+
+ @SuppressWarnings("InnerClassMayBeStatic")
+ // tag::streamSendNonBlocking[]
+ @WebSocket
+ public class StreamSendNonBlockingEndpoint
+ {
+ @OnWebSocketMessage
+ public void onText(Session session, String text)
+ {
+ RemoteEndpoint remote = session.getRemote();
+ new Sender(remote).iterate();
+ }
+
+ private class Sender extends IteratingCallback implements WriteCallback // <1>
+ {
+ private final RemoteEndpoint remote;
+ private boolean finished;
+
+ private Sender(RemoteEndpoint remote)
+ {
+ this.remote = remote;
+ }
+
+ @Override
+ protected Action process() throws Throwable // <2>
+ {
+ if (finished)
+ return Action.SUCCEEDED;
+
+ ByteBuffer chunk = readChunkToSend();
+ if (chunk == null)
+ {
+ // No more bytes, finish the WebSocket message.
+ remote.sendPartialBytes(ByteBuffer.allocate(0), true, this); // <3>
+ finished = true;
+ return Action.SCHEDULED;
+ }
+ else
+ {
+ // Send the chunk.
+ remote.sendPartialBytes(ByteBuffer.allocate(0), false, this); // <3>
+ return Action.SCHEDULED;
+ }
+ }
+
+ @Override
+ public void writeSuccess()
+ {
+ // When the send succeeds, succeed this IteratingCallback.
+ succeeded();
+ }
+
+ @Override
+ public void writeFailed(Throwable x)
+ {
+ // When the send fails, fail this IteratingCallback.
+ failed(x);
+ }
+
+ @Override
+ protected void onCompleteFailure(Throwable x)
+ {
+ x.printStackTrace();
+ }
+ }
+ }
+ // end::streamSendNonBlocking[]
+
+ @SuppressWarnings("InnerClassMayBeStatic")
+ // tag::pingPongListener[]
+ public class RoundTripListenerEndpoint implements WebSocketPingPongListener // <1>
+ {
+ @Override
+ public void onWebSocketConnect(Session session)
+ {
+ // Send to the remote peer the local nanoTime.
+ ByteBuffer buffer = ByteBuffer.allocate(8).putLong(NanoTime.now()).flip();
+ session.getRemote().sendPing(buffer, WriteCallback.NOOP);
+ }
+
+ @Override
+ public void onWebSocketPong(ByteBuffer payload)
+ {
+ // The remote peer echoed back the local nanoTime.
+ long start = payload.getLong();
+
+ // Calculate the round-trip time.
+ long roundTrip = NanoTime.since(start);
+ }
+ }
+ // end::pingPongListener[]
+
+ @SuppressWarnings("InnerClassMayBeStatic")
+ // tag::sessionClose[]
+ @WebSocket
+ public class CloseEndpoint
+ {
+ @OnWebSocketMessage
+ public void onText(Session session, String text)
+ {
+ if ("close".equalsIgnoreCase(text))
+ session.close(StatusCode.NORMAL, "bye");
+ }
+ }
+ // end::sessionClose[]
+
+ private static void forwardToREST(String payload, boolean fin)
+ {
+ }
+
+ private static void forwardToREST(Reader reader)
+ {
+ }
+
+ private static void appendToFile(ByteBuffer payload, boolean fin)
+ {
+ }
+
+ private static void appendToFile(InputStream stream)
+ {
+ }
+
+ private static void disposeResources()
+ {
+ }
+
+ private static void savePNGImage(byte[] payload, int offset, int length)
+ {
+ }
+
+ private static ByteBuffer readImageFromFile()
+ {
+ return null;
+ }
+
+ private static ByteBuffer readChunkToSend()
+ {
+ return null;
+ }
+}
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/ClientConnectorDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/ClientConnectorDocs.java
new file mode 100644
index 00000000000..48b155b91e7
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/ClientConnectorDocs.java
@@ -0,0 +1,439 @@
+//
+// ========================================================================
+// 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.docs.programming.client;
+
+import java.io.ByteArrayOutputStream;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+import static java.lang.System.Logger.Level.INFO;
+
+@SuppressWarnings("unused")
+public class ClientConnectorDocs
+{
+ public void simplest() throws Exception
+ {
+ // tag::simplest[]
+ ClientConnector clientConnector = new ClientConnector();
+ clientConnector.start();
+ // end::simplest[]
+ }
+
+ public void typical() throws Exception
+ {
+ // tag::typical[]
+ // Create and configure the SslContextFactory.
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+ sslContextFactory.addExcludeProtocols("TLSv1", "TLSv1.1");
+
+ // Create and configure the thread pool.
+ QueuedThreadPool threadPool = new QueuedThreadPool();
+ threadPool.setName("client");
+
+ // Create and configure the ClientConnector.
+ ClientConnector clientConnector = new ClientConnector();
+ clientConnector.setSslContextFactory(sslContextFactory);
+ clientConnector.setExecutor(threadPool);
+ clientConnector.start();
+ // end::typical[]
+ }
+
+ public void advanced() throws Exception
+ {
+ // tag::advanced[]
+ class CustomClientConnector extends ClientConnector
+ {
+ @Override
+ protected SelectorManager newSelectorManager()
+ {
+ return new ClientSelectorManager(getExecutor(), getScheduler(), getSelectors())
+ {
+ @Override
+ protected void endPointOpened(EndPoint endpoint)
+ {
+ System.getLogger("endpoint").log(INFO, "opened %s", endpoint);
+ }
+
+ @Override
+ protected void endPointClosed(EndPoint endpoint)
+ {
+ System.getLogger("endpoint").log(INFO, "closed %s", endpoint);
+ }
+ };
+ }
+ }
+
+ // Create and configure the thread pool.
+ QueuedThreadPool threadPool = new QueuedThreadPool();
+ threadPool.setName("client");
+
+ // Create and configure the scheduler.
+ Scheduler scheduler = new ScheduledExecutorScheduler("scheduler-client", false);
+
+ // Create and configure the custom ClientConnector.
+ CustomClientConnector clientConnector = new CustomClientConnector();
+ clientConnector.setExecutor(threadPool);
+ clientConnector.setScheduler(scheduler);
+ clientConnector.start();
+ // end::advanced[]
+ }
+
+ public void connect() throws Exception
+ {
+ // tag::connect[]
+ class CustomConnection extends AbstractConnection
+ {
+ public CustomConnection(EndPoint endPoint, Executor executor)
+ {
+ super(endPoint, executor);
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ System.getLogger("connection").log(INFO, "Opened connection {0}", this);
+ }
+
+ @Override
+ public void onFillable()
+ {
+ }
+ }
+
+ ClientConnector clientConnector = new ClientConnector();
+ clientConnector.start();
+
+ String host = "serverHost";
+ int port = 8080;
+ SocketAddress address = new InetSocketAddress(host, port);
+
+ // The ClientConnectionFactory that creates CustomConnection instances.
+ ClientConnectionFactory connectionFactory = (endPoint, context) ->
+ {
+ System.getLogger("connection").log(INFO, "Creating connection for {0}", endPoint);
+ return new CustomConnection(endPoint, clientConnector.getExecutor());
+ };
+
+ // The Promise to notify of connection creation success or failure.
+ CompletableFuture connectionPromise = new Promise.Completable<>();
+
+ // Populate the context with the mandatory keys to create and obtain connections.
+ Map context = new HashMap<>();
+ context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory);
+ context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, connectionPromise);
+ clientConnector.connect(address, context);
+
+ // Use the Connection when it's available.
+
+ // Use it in a non-blocking way via CompletableFuture APIs.
+ connectionPromise.whenComplete((connection, failure) ->
+ {
+ System.getLogger("connection").log(INFO, "Created connection for {0}", connection);
+ });
+
+ // Alternatively, you can block waiting for the connection (or a failure).
+ // CustomConnection connection = connectionPromise.get();
+ // end::connect[]
+ }
+
+ public void telnet() throws Exception
+ {
+ // tag::telnet[]
+ class TelnetConnection extends AbstractConnection
+ {
+ private final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ private Consumer consumer;
+
+ public TelnetConnection(EndPoint endPoint, Executor executor)
+ {
+ super(endPoint, executor);
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+
+ // Declare interest for fill events.
+ fillInterested();
+ }
+
+ @Override
+ public void onFillable()
+ {
+ try
+ {
+ ByteBuffer buffer = BufferUtil.allocate(1024);
+ while (true)
+ {
+ int filled = getEndPoint().fill(buffer);
+ if (filled > 0)
+ {
+ while (buffer.hasRemaining())
+ {
+ // Search for newline.
+ byte read = buffer.get();
+ if (read == '\n')
+ {
+ // Notify the consumer of the line.
+ consumer.accept(bytes.toString(StandardCharsets.UTF_8));
+ bytes.reset();
+ }
+ else
+ {
+ bytes.write(read);
+ }
+ }
+ }
+ else if (filled == 0)
+ {
+ // No more bytes to fill, declare
+ // again interest for fill events.
+ fillInterested();
+ return;
+ }
+ else
+ {
+ // The other peer closed the
+ // connection, close it back.
+ getEndPoint().close();
+ return;
+ }
+ }
+ }
+ catch (Exception x)
+ {
+ getEndPoint().close(x);
+ }
+ }
+
+ public void onLine(Consumer consumer)
+ {
+ this.consumer = consumer;
+ }
+
+ public void writeLine(String line, Callback callback)
+ {
+ line = line + "\r\n";
+ getEndPoint().write(callback, ByteBuffer.wrap(line.getBytes(StandardCharsets.UTF_8)));
+ }
+ }
+
+ ClientConnector clientConnector = new ClientConnector();
+ clientConnector.start();
+
+ String host = "wikipedia.org";
+ int port = 80;
+ SocketAddress address = new InetSocketAddress(host, port);
+
+ ClientConnectionFactory connectionFactory = (endPoint, context) ->
+ new TelnetConnection(endPoint, clientConnector.getExecutor());
+
+ CompletableFuture connectionPromise = new Promise.Completable<>();
+
+ Map context = new HashMap<>();
+ context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory);
+ context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, connectionPromise);
+ clientConnector.connect(address, context);
+
+ connectionPromise.whenComplete((connection, failure) ->
+ {
+ if (failure == null)
+ {
+ // Register a listener that receives string lines.
+ connection.onLine(line -> System.getLogger("app").log(INFO, "line: {0}", line));
+
+ // Write a line.
+ connection.writeLine("" +
+ "GET / HTTP/1.0\r\n" +
+ "", Callback.NOOP);
+ }
+ else
+ {
+ failure.printStackTrace();
+ }
+ });
+ // end::telnet[]
+ }
+
+ public void tlsTelnet() throws Exception
+ {
+ // tag::tlsTelnet[]
+ class TelnetConnection extends AbstractConnection
+ {
+ private final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ private Consumer consumer;
+
+ public TelnetConnection(EndPoint endPoint, Executor executor)
+ {
+ super(endPoint, executor);
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+
+ // Declare interest for fill events.
+ fillInterested();
+ }
+
+ @Override
+ public void onFillable()
+ {
+ try
+ {
+ ByteBuffer buffer = BufferUtil.allocate(1024);
+ while (true)
+ {
+ int filled = getEndPoint().fill(buffer);
+ if (filled > 0)
+ {
+ while (buffer.hasRemaining())
+ {
+ // Search for newline.
+ byte read = buffer.get();
+ if (read == '\n')
+ {
+ // Notify the consumer of the line.
+ consumer.accept(bytes.toString(StandardCharsets.UTF_8));
+ bytes.reset();
+ }
+ else
+ {
+ bytes.write(read);
+ }
+ }
+ }
+ else if (filled == 0)
+ {
+ // No more bytes to fill, declare
+ // again interest for fill events.
+ fillInterested();
+ return;
+ }
+ else
+ {
+ // The other peer closed the
+ // connection, close it back.
+ getEndPoint().close();
+ return;
+ }
+ }
+ }
+ catch (Exception x)
+ {
+ getEndPoint().close(x);
+ }
+ }
+
+ public void onLine(Consumer consumer)
+ {
+ this.consumer = consumer;
+ }
+
+ public void writeLine(String line, Callback callback)
+ {
+ line = line + "\r\n";
+ getEndPoint().write(callback, ByteBuffer.wrap(line.getBytes(StandardCharsets.UTF_8)));
+ }
+ }
+
+ ClientConnector clientConnector = new ClientConnector();
+ clientConnector.start();
+
+ // Use port 443 to contact the server using encrypted HTTP.
+ String host = "wikipedia.org";
+ int port = 443;
+ SocketAddress address = new InetSocketAddress(host, port);
+
+ ClientConnectionFactory connectionFactory = (endPoint, context) ->
+ new TelnetConnection(endPoint, clientConnector.getExecutor());
+
+ // Wrap the "telnet" ClientConnectionFactory with the SslClientConnectionFactory.
+ connectionFactory = new SslClientConnectionFactory(clientConnector.getSslContextFactory(),
+ clientConnector.getByteBufferPool(), clientConnector.getExecutor(), connectionFactory);
+
+ // We will obtain a SslConnection now.
+ CompletableFuture connectionPromise = new Promise.Completable<>();
+
+ Map context = new HashMap<>();
+ context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory);
+ context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, connectionPromise);
+ clientConnector.connect(address, context);
+
+ connectionPromise.whenComplete((sslConnection, failure) ->
+ {
+ if (failure == null)
+ {
+ // Unwrap the SslConnection to access the "line" APIs in TelnetConnection.
+ TelnetConnection connection = (TelnetConnection)sslConnection.getDecryptedEndPoint().getConnection();
+ // Register a listener that receives string lines.
+ connection.onLine(line -> System.getLogger("app").log(INFO, "line: {0}", line));
+
+ // Write a line.
+ connection.writeLine("" +
+ "GET / HTTP/1.0\r\n" +
+ "", Callback.NOOP);
+ }
+ else
+ {
+ failure.printStackTrace();
+ }
+ });
+ // end::tlsTelnet[]
+ }
+
+ public void unixDomain() throws Exception
+ {
+ // tag::unixDomain[]
+ // This is the path where the server "listens" on.
+ Path unixDomainPath = Path.of("/path/to/server.sock");
+
+ // Creates a ClientConnector that uses Unix-Domain
+ // sockets, not the network, to connect to the server.
+ ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath);
+ clientConnector.start();
+ // end::unixDomain[]
+ }
+
+ public static void main(String[] args) throws Exception
+ {
+ new ClientConnectorDocs().tlsTelnet();
+ }
+}
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java
new file mode 100644
index 00000000000..2954db45189
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java
@@ -0,0 +1,930 @@
+//
+// ========================================================================
+// 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.docs.programming.client.http;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.CookieStore;
+import java.net.HttpCookie;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.function.LongConsumer;
+
+import org.eclipse.jetty.client.ConnectionPool;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpClientTransport;
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.HttpProxy;
+import org.eclipse.jetty.client.ProxyConfiguration;
+import org.eclipse.jetty.client.RoundRobinConnectionPool;
+import org.eclipse.jetty.client.Socks5;
+import org.eclipse.jetty.client.Socks5Proxy;
+import org.eclipse.jetty.client.api.Authentication;
+import org.eclipse.jetty.client.api.AuthenticationStore;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
+import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
+import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
+import org.eclipse.jetty.client.util.AsyncRequestContent;
+import org.eclipse.jetty.client.util.BasicAuthentication;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.client.util.BytesRequestContent;
+import org.eclipse.jetty.client.util.DigestAuthentication;
+import org.eclipse.jetty.client.util.FutureResponseListener;
+import org.eclipse.jetty.client.util.InputStreamRequestContent;
+import org.eclipse.jetty.client.util.InputStreamResponseListener;
+import org.eclipse.jetty.client.util.OutputStreamRequestContent;
+import org.eclipse.jetty.client.util.PathRequestContent;
+import org.eclipse.jetty.client.util.StringRequestContent;
+import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http2.client.HTTP2Client;
+import org.eclipse.jetty.http2.client.http.ClientConnectionFactoryOverHTTP2;
+import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2;
+import org.eclipse.jetty.http3.client.HTTP3Client;
+import org.eclipse.jetty.http3.client.http.HttpClientTransportOverHTTP3;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.HttpCookieStore;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+import static java.lang.System.Logger.Level.INFO;
+
+@SuppressWarnings("unused")
+public class HTTPClientDocs
+{
+ public void start() throws Exception
+ {
+ // tag::start[]
+ // Instantiate HttpClient.
+ HttpClient httpClient = new HttpClient();
+
+ // Configure HttpClient, for example:
+ httpClient.setFollowRedirects(false);
+
+ // Start HttpClient.
+ httpClient.start();
+ // end::start[]
+ }
+
+ public void stop() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+ // tag::stop[]
+ // Stop HttpClient.
+ httpClient.stop();
+ // end::stop[]
+ }
+
+ public void stopFromOtherThread() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+ // tag::stopFromOtherThread[]
+ // Stop HttpClient from a new thread.
+ // Use LifeCycle.stop(...) to rethrow checked exceptions as unchecked.
+ new Thread(() -> LifeCycle.stop(httpClient)).start();
+ // end::stopFromOtherThread[]
+ }
+
+ public void tlsExplicit() throws Exception
+ {
+ // tag::tlsExplicit[]
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+
+ ClientConnector clientConnector = new ClientConnector();
+ clientConnector.setSslContextFactory(sslContextFactory);
+
+ HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector));
+ httpClient.start();
+ // end::tlsExplicit[]
+ }
+
+ public void tlsNoValidation()
+ {
+ // tag::tlsNoValidation[]
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+ // Disable the validation of the server host name at the TLS level.
+ sslContextFactory.setEndpointIdentificationAlgorithm(null);
+ // end::tlsNoValidation[]
+ }
+
+ public void tlsAppValidation()
+ {
+ // tag::tlsAppValidation[]
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+ // Only allow to connect to subdomains of domain.com.
+ sslContextFactory.setHostnameVerifier((hostName, session) -> hostName.endsWith(".domain.com"));
+ // end::tlsAppValidation[]
+ }
+
+ public void simpleBlockingGet() throws Exception
+ {
+ // tag::simpleBlockingGet[]
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // Perform a simple GET and wait for the response.
+ ContentResponse response = httpClient.GET("http://domain.com/path?query");
+ // end::simpleBlockingGet[]
+ }
+
+ public void headFluent() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::headFluent[]
+ ContentResponse response = httpClient.newRequest("http://domain.com/path?query")
+ .method(HttpMethod.HEAD)
+ .agent("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:17.0) Gecko/20100101 Firefox/17.0")
+ .send();
+ // end::headFluent[]
+ }
+
+ public void headNonFluent() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::headNonFluent[]
+ Request request = httpClient.newRequest("http://domain.com/path?query");
+ request.method(HttpMethod.HEAD);
+ request.agent("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:17.0) Gecko/20100101 Firefox/17.0");
+ ContentResponse response = request.send();
+ // end::headNonFluent[]
+ }
+
+ public void postFluent() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::postFluent[]
+ ContentResponse response = httpClient.POST("http://domain.com/entity/1")
+ .param("p", "value")
+ .send();
+ // end::postFluent[]
+ }
+
+ public void fileFluent() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::fileFluent[]
+ ContentResponse response = httpClient.POST("http://domain.com/upload")
+ .file(Paths.get("file_to_upload.txt"), "text/plain")
+ .send();
+ // end::fileFluent[]
+ }
+
+ public void totalTimeout() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::totalTimeout[]
+ ContentResponse response = httpClient.newRequest("http://domain.com/path?query")
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+ // end::totalTimeout[]
+ }
+
+ public void simpleNonBlocking() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::simpleNonBlocking[]
+ httpClient.newRequest("http://domain.com/path")
+ .send(result ->
+ {
+ // Your logic here
+ });
+ // end::simpleNonBlocking[]
+ }
+
+ public void nonBlockingTotalTimeout() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::nonBlockingTotalTimeout[]
+ httpClient.newRequest("http://domain.com/path")
+ .timeout(3, TimeUnit.SECONDS)
+ .send(result ->
+ {
+ /* Your logic here */
+ });
+ // end::nonBlockingTotalTimeout[]
+ }
+
+ // @checkstyle-disable-check : LeftCurly
+ public void listeners() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::listeners[]
+ httpClient.newRequest("http://domain.com/path")
+ // Add request hooks.
+ .onRequestQueued(request -> { /* ... */ })
+ .onRequestBegin(request -> { /* ... */ })
+ .onRequestHeaders(request -> { /* ... */ })
+ .onRequestCommit(request -> { /* ... */ })
+ .onRequestContent((request, content) -> { /* ... */ })
+ .onRequestFailure((request, failure) -> { /* ... */ })
+ .onRequestSuccess(request -> { /* ... */ })
+ // Add response hooks.
+ .onResponseBegin(response -> { /* ... */ })
+ .onResponseHeader((response, field) -> true)
+ .onResponseHeaders(response -> { /* ... */ })
+ .onResponseContentAsync((response, buffer, callback) -> callback.succeeded())
+ .onResponseFailure((response, failure) -> { /* ... */ })
+ .onResponseSuccess(response -> { /* ... */ })
+ // Result hook.
+ .send(result -> { /* ... */ });
+ // end::listeners[]
+ }
+
+ public void pathRequestContent() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::pathRequestContent[]
+ ContentResponse response = httpClient.POST("http://domain.com/upload")
+ .body(new PathRequestContent("text/plain", Paths.get("file_to_upload.txt")))
+ .send();
+ // end::pathRequestContent[]
+ }
+
+ public void inputStreamRequestContent() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::inputStreamRequestContent[]
+ ContentResponse response = httpClient.POST("http://domain.com/upload")
+ .body(new InputStreamRequestContent("text/plain", new FileInputStream("file_to_upload.txt")))
+ .send();
+ // end::inputStreamRequestContent[]
+ }
+
+ public void bytesStringRequestContent() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ byte[] bytes = new byte[1024];
+ String string = new String(bytes);
+ // tag::bytesStringRequestContent[]
+ ContentResponse bytesResponse = httpClient.POST("http://domain.com/upload")
+ .body(new BytesRequestContent("text/plain", bytes))
+ .send();
+
+ ContentResponse stringResponse = httpClient.POST("http://domain.com/upload")
+ .body(new StringRequestContent("text/plain", string))
+ .send();
+ // end::bytesStringRequestContent[]
+ }
+
+ public void asyncRequestContent() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::asyncRequestContent[]
+ AsyncRequestContent content = new AsyncRequestContent();
+ httpClient.POST("http://domain.com/upload")
+ .body(content)
+ .send(result ->
+ {
+ // Your logic here
+ });
+
+ // Content not available yet here.
+
+ // An event happens in some other class, in some other thread.
+ class ContentPublisher
+ {
+ void publish(ByteBufferPool bufferPool, byte[] bytes, boolean lastContent)
+ {
+ // Wrap the bytes into a new ByteBuffer.
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+
+ // Offer the content, and release the ByteBuffer
+ // to the pool when the Callback is completed.
+ content.offer(buffer, Callback.from(() -> bufferPool.release(buffer)));
+
+ // Close AsyncRequestContent when all the content is arrived.
+ if (lastContent)
+ content.close();
+ }
+ }
+ // end::asyncRequestContent[]
+ }
+
+ public void outputStreamRequestContent() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::outputStreamRequestContent[]
+ OutputStreamRequestContent content = new OutputStreamRequestContent();
+
+ // Use try-with-resources to close the OutputStream when all content is written.
+ try (OutputStream output = content.getOutputStream())
+ {
+ httpClient.POST("http://localhost:8080/")
+ .body(content)
+ .send(result ->
+ {
+ // Your logic here
+ });
+
+ // Content not available yet here.
+
+ // Content is now available.
+ byte[] bytes = new byte[]{'h', 'e', 'l', 'l', 'o'};
+ output.write(bytes);
+ }
+ // End of try-with-resource, output.close() called automatically to signal end of content.
+ // end::outputStreamRequestContent[]
+ }
+
+ public void futureResponseListener() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::futureResponseListener[]
+ Request request = httpClient.newRequest("http://domain.com/path");
+
+ // Limit response content buffer to 512 KiB.
+ FutureResponseListener listener = new FutureResponseListener(request, 512 * 1024);
+
+ request.send(listener);
+
+ // Wait at most 5 seconds for request+response to complete.
+ ContentResponse response = listener.get(5, TimeUnit.SECONDS);
+ // end::futureResponseListener[]
+ }
+
+ public void bufferingResponseListener() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::bufferingResponseListener[]
+ httpClient.newRequest("http://domain.com/path")
+ // Buffer response content up to 8 MiB
+ .send(new BufferingResponseListener(8 * 1024 * 1024)
+ {
+ @Override
+ public void onComplete(Result result)
+ {
+ if (!result.isFailed())
+ {
+ byte[] responseContent = getContent();
+ // Your logic here
+ }
+ }
+ });
+ // end::bufferingResponseListener[]
+ }
+
+ public void inputStreamResponseListener() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::inputStreamResponseListener[]
+ InputStreamResponseListener listener = new InputStreamResponseListener();
+ httpClient.newRequest("http://domain.com/path")
+ .send(listener);
+
+ // Wait for the response headers to arrive.
+ Response response = listener.get(5, TimeUnit.SECONDS);
+
+ // Look at the response before streaming the content.
+ if (response.getStatus() == HttpStatus.OK_200)
+ {
+ // Use try-with-resources to close input stream.
+ try (InputStream responseContent = listener.getInputStream())
+ {
+ // Your logic here
+ }
+ }
+ else
+ {
+ response.abort(new IOException("Unexpected HTTP response"));
+ }
+ // end::inputStreamResponseListener[]
+ }
+
+ public void demandedContentListener() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ String host1 = "localhost";
+ String host2 = "localhost";
+ int port1 = 8080;
+ int port2 = 8080;
+ // tag::demandedContentListener[]
+ // Prepare a request to server1, the source.
+ Request request1 = httpClient.newRequest(host1, port1)
+ .path("/source");
+
+ // Prepare a request to server2, the sink.
+ AsyncRequestContent content2 = new AsyncRequestContent();
+ Request request2 = httpClient.newRequest(host2, port2)
+ .path("/sink")
+ .body(content2);
+
+ request1.onResponseContentDemanded(new Response.DemandedContentListener()
+ {
+ @Override
+ public void onBeforeContent(Response response, LongConsumer demand)
+ {
+ request2.onRequestCommit(request ->
+ {
+ // Only when the request to server2 has been sent,
+ // then demand response content from server1.
+ demand.accept(1);
+ });
+
+ // Send the request to server2.
+ request2.send(result -> System.getLogger("forwarder").log(INFO, "Forwarding to server2 complete"));
+ }
+
+ @Override
+ public void onContent(Response response, LongConsumer demand, ByteBuffer content, Callback callback)
+ {
+ // When response content is received from server1, forward it to server2.
+ content2.offer(content, Callback.from(() ->
+ {
+ // When the request content to server2 is sent,
+ // succeed the callback to recycle the buffer.
+ callback.succeeded();
+ // Then demand more response content from server1.
+ demand.accept(1);
+ }, callback::failed));
+ }
+ });
+
+ // When the response content from server1 is complete,
+ // complete also the request content to server2.
+ request1.onResponseSuccess(response -> content2.close());
+
+ // Send the request to server1.
+ request1.send(result -> System.getLogger("forwarder").log(INFO, "Sourcing from server1 complete"));
+ // end::demandedContentListener[]
+ }
+
+ public void getCookies() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::getCookies[]
+ CookieStore cookieStore = httpClient.getCookieStore();
+ List cookies = cookieStore.get(URI.create("http://domain.com/path"));
+ // end::getCookies[]
+ }
+
+ public void setCookie() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::setCookie[]
+ CookieStore cookieStore = httpClient.getCookieStore();
+ HttpCookie cookie = new HttpCookie("foo", "bar");
+ cookie.setDomain("domain.com");
+ cookie.setPath("/");
+ cookie.setMaxAge(TimeUnit.DAYS.toSeconds(1));
+ cookieStore.add(URI.create("http://domain.com"), cookie);
+ // end::setCookie[]
+ }
+
+ public void requestCookie() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::requestCookie[]
+ ContentResponse response = httpClient.newRequest("http://domain.com/path")
+ .cookie(new HttpCookie("foo", "bar"))
+ .send();
+ // end::requestCookie[]
+ }
+
+ public void removeCookie() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::removeCookie[]
+ CookieStore cookieStore = httpClient.getCookieStore();
+ URI uri = URI.create("http://domain.com");
+ List cookies = cookieStore.get(uri);
+ for (HttpCookie cookie : cookies)
+ {
+ cookieStore.remove(uri, cookie);
+ }
+ // end::removeCookie[]
+ }
+
+ public void emptyCookieStore() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::emptyCookieStore[]
+ httpClient.setCookieStore(new HttpCookieStore.Empty());
+ // end::emptyCookieStore[]
+ }
+
+ public void filteringCookieStore() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::filteringCookieStore[]
+ class GoogleOnlyCookieStore extends HttpCookieStore
+ {
+ @Override
+ public void add(URI uri, HttpCookie cookie)
+ {
+ if (uri.getHost().endsWith("google.com"))
+ super.add(uri, cookie);
+ }
+ }
+
+ httpClient.setCookieStore(new GoogleOnlyCookieStore());
+ // end::filteringCookieStore[]
+ }
+
+ public void addAuthentication() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::addAuthentication[]
+ // Add authentication credentials.
+ AuthenticationStore auth = httpClient.getAuthenticationStore();
+
+ URI uri1 = new URI("http://mydomain.com/secure");
+ auth.addAuthentication(new BasicAuthentication(uri1, "MyRealm", "userName1", "password1"));
+
+ URI uri2 = new URI("http://otherdomain.com/admin");
+ auth.addAuthentication(new BasicAuthentication(uri1, "AdminRealm", "admin", "password"));
+ // end::addAuthentication[]
+ }
+
+ public void clearResults() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::clearResults[]
+ httpClient.getAuthenticationStore().clearAuthenticationResults();
+ // end::clearResults[]
+ }
+
+ public void preemptedResult() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::preemptedResult[]
+ AuthenticationStore auth = httpClient.getAuthenticationStore();
+ URI uri = URI.create("http://domain.com/secure");
+ auth.addAuthenticationResult(new BasicAuthentication.BasicResult(uri, "username", "password"));
+ // end::preemptedResult[]
+ }
+
+ public void requestPreemptedResult() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::requestPreemptedResult[]
+ URI uri = URI.create("http://domain.com/secure");
+ Authentication.Result authn = new BasicAuthentication.BasicResult(uri, "username", "password");
+ Request request = httpClient.newRequest(uri);
+ authn.apply(request);
+ request.send();
+ // end::requestPreemptedResult[]
+ }
+
+ public void proxy() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::proxy[]
+ HttpProxy proxy = new HttpProxy("proxyHost", 8888);
+
+ // Do not proxy requests for localhost:8080.
+ proxy.getExcludedAddresses().add("localhost:8080");
+
+ // Add the new proxy to the list of proxies already registered.
+ ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration();
+ proxyConfig.addProxy(proxy);
+
+ ContentResponse response = httpClient.GET("http://domain.com/path");
+ // end::proxy[]
+ }
+
+ public void proxySocks5() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::proxySocks5[]
+ Socks5Proxy proxy = new Socks5Proxy("proxyHost", 8888);
+ String socks5User = "jetty";
+ String socks5Pass = "secret";
+ var socks5AuthenticationFactory = new Socks5.UsernamePasswordAuthenticationFactory(socks5User, socks5Pass);
+ // Add the authentication method to the proxy.
+ proxy.putAuthenticationFactory(socks5AuthenticationFactory);
+
+ // Do not proxy requests for localhost:8080.
+ proxy.getExcludedAddresses().add("localhost:8080");
+
+ // Add the new proxy to the list of proxies already registered.
+ ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration();
+ proxyConfig.addProxy(proxy);
+
+ ContentResponse response = httpClient.GET("http://domain.com/path");
+ // end::proxySocks5[]
+ }
+
+ public void proxyAuthentication() throws Exception
+ {
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // tag::proxyAuthentication[]
+ AuthenticationStore auth = httpClient.getAuthenticationStore();
+
+ // Proxy credentials.
+ URI proxyURI = new URI("http://proxy.net:8080");
+ auth.addAuthentication(new BasicAuthentication(proxyURI, "ProxyRealm", "proxyUser", "proxyPass"));
+
+ // Server credentials.
+ URI serverURI = new URI("http://domain.com/secure");
+ auth.addAuthentication(new DigestAuthentication(serverURI, "ServerRealm", "serverUser", "serverPass"));
+
+ // Proxy configuration.
+ ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration();
+ HttpProxy proxy = new HttpProxy("proxy.net", 8080);
+ proxyConfig.addProxy(proxy);
+
+ ContentResponse response = httpClient.newRequest(serverURI).send();
+ // end::proxyAuthentication[]
+ }
+
+ public void defaultTransport() throws Exception
+ {
+ // tag::defaultTransport[]
+ // No transport specified, using default.
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+ // end::defaultTransport[]
+ }
+
+ public void http11Transport() throws Exception
+ {
+ // tag::http11Transport[]
+ // Configure HTTP/1.1 transport.
+ HttpClientTransportOverHTTP transport = new HttpClientTransportOverHTTP();
+ transport.setHeaderCacheSize(16384);
+
+ HttpClient client = new HttpClient(transport);
+ client.start();
+ // end::http11Transport[]
+ }
+
+ public void http2Transport() throws Exception
+ {
+ // tag::http2Transport[]
+ // The HTTP2Client powers the HTTP/2 transport.
+ HTTP2Client h2Client = new HTTP2Client();
+ h2Client.setInitialSessionRecvWindow(64 * 1024 * 1024);
+
+ // Create and configure the HTTP/2 transport.
+ HttpClientTransportOverHTTP2 transport = new HttpClientTransportOverHTTP2(h2Client);
+ transport.setUseALPN(true);
+
+ HttpClient client = new HttpClient(transport);
+ client.start();
+ // end::http2Transport[]
+ }
+
+ public void http3Transport() throws Exception
+ {
+ // tag::http3Transport[]
+ // The HTTP3Client powers the HTTP/3 transport.
+ HTTP3Client h3Client = new HTTP3Client();
+ h3Client.getQuicConfiguration().setSessionRecvWindow(64 * 1024 * 1024);
+
+ // Create and configure the HTTP/3 transport.
+ HttpClientTransportOverHTTP3 transport = new HttpClientTransportOverHTTP3(h3Client);
+
+ HttpClient client = new HttpClient(transport);
+ client.start();
+ // end::http3Transport[]
+ }
+
+ public void fcgiTransport() throws Exception
+ {
+ // tag::fcgiTransport[]
+ String scriptRoot = "/var/www/wordpress";
+ HttpClientTransportOverFCGI transport = new HttpClientTransportOverFCGI(scriptRoot);
+
+ HttpClient client = new HttpClient(transport);
+ client.start();
+ // end::fcgiTransport[]
+ }
+
+ public void dynamicDefault() throws Exception
+ {
+ // tag::dynamicDefault[]
+ // Dynamic transport speaks HTTP/1.1 by default.
+ HttpClientTransportDynamic transport = new HttpClientTransportDynamic();
+
+ HttpClient client = new HttpClient(transport);
+ client.start();
+ // end::dynamicDefault[]
+ }
+
+ public void dynamicOneProtocol()
+ {
+ // tag::dynamicOneProtocol[]
+ ClientConnector connector = new ClientConnector();
+
+ // Equivalent to HttpClientTransportOverHTTP.
+ HttpClientTransportDynamic http11Transport = new HttpClientTransportDynamic(connector, HttpClientConnectionFactory.HTTP11);
+
+ // Equivalent to HttpClientTransportOverHTTP2.
+ HTTP2Client http2Client = new HTTP2Client(connector);
+ HttpClientTransportDynamic http2Transport = new HttpClientTransportDynamic(connector, new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client));
+ // end::dynamicOneProtocol[]
+ }
+
+ public void dynamicH1H2() throws Exception
+ {
+ // tag::dynamicH1H2[]
+ ClientConnector connector = new ClientConnector();
+
+ ClientConnectionFactory.Info http1 = HttpClientConnectionFactory.HTTP11;
+
+ HTTP2Client http2Client = new HTTP2Client(connector);
+ ClientConnectionFactoryOverHTTP2.HTTP2 http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
+
+ HttpClientTransportDynamic transport = new HttpClientTransportDynamic(connector, http1, http2);
+
+ HttpClient client = new HttpClient(transport);
+ client.start();
+ // end::dynamicH1H2[]
+ }
+
+ public void dynamicClearText() throws Exception
+ {
+ // tag::dynamicClearText[]
+ ClientConnector connector = new ClientConnector();
+ ClientConnectionFactory.Info http1 = HttpClientConnectionFactory.HTTP11;
+ HTTP2Client http2Client = new HTTP2Client(connector);
+ ClientConnectionFactoryOverHTTP2.HTTP2 http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
+ HttpClientTransportDynamic transport = new HttpClientTransportDynamic(connector, http1, http2);
+ HttpClient client = new HttpClient(transport);
+ client.start();
+
+ // The server supports both HTTP/1.1 and HTTP/2 clear-text on port 8080.
+
+ // Make a clear-text request without explicit version.
+ // The first protocol specified to HttpClientTransportDynamic
+ // is picked, in this example will be HTTP/1.1.
+ ContentResponse http1Response = client.newRequest("host", 8080).send();
+
+ // Make a clear-text request with explicit version.
+ // Clear-text HTTP/2 is used for this request.
+ ContentResponse http2Response = client.newRequest("host", 8080)
+ // Specify the version explicitly.
+ .version(HttpVersion.HTTP_2)
+ .send();
+
+ // Make a clear-text upgrade request from HTTP/1.1 to HTTP/2.
+ // The request will start as HTTP/1.1, but the response will be HTTP/2.
+ ContentResponse upgradedResponse = client.newRequest("host", 8080)
+ .headers(headers -> headers
+ .put(HttpHeader.UPGRADE, "h2c")
+ .put(HttpHeader.HTTP2_SETTINGS, "")
+ .put(HttpHeader.CONNECTION, "Upgrade, HTTP2-Settings"))
+ .send();
+ // end::dynamicClearText[]
+ }
+
+ public void getConnectionPool() throws Exception
+ {
+ // tag::getConnectionPool[]
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ ConnectionPool connectionPool = httpClient.getDestinations().stream()
+ // Cast to HttpDestination.
+ .map(HttpDestination.class::cast)
+ // Find the destination by filtering on the Origin.
+ .filter(destination -> destination.getOrigin().getAddress().getHost().equals("domain.com"))
+ .findAny()
+ // Get the ConnectionPool.
+ .map(HttpDestination::getConnectionPool)
+ .orElse(null);
+ // end::getConnectionPool[]
+ }
+
+ public void setConnectionPool() throws Exception
+ {
+ // tag::setConnectionPool[]
+ HttpClient httpClient = new HttpClient();
+ httpClient.start();
+
+ // The max number of connections in the pool.
+ int maxConnectionsPerDestination = httpClient.getMaxConnectionsPerDestination();
+
+ // The max number of requests per connection (multiplexing).
+ // Start with 1, since this value is dynamically set to larger values if
+ // the transport supports multiplexing requests on the same connection.
+ int maxRequestsPerConnection = 1;
+
+ HttpClientTransport transport = httpClient.getTransport();
+
+ // Set the ConnectionPool.Factory using a lambda.
+ transport.setConnectionPoolFactory(destination ->
+ new RoundRobinConnectionPool(destination,
+ maxConnectionsPerDestination,
+ destination,
+ maxRequestsPerConnection));
+ // end::setConnectionPool[]
+ }
+
+ public void unixDomain() throws Exception
+ {
+ // tag::unixDomain[]
+ // This is the path where the server "listens" on.
+ Path unixDomainPath = Path.of("/path/to/server.sock");
+
+ // Creates a ClientConnector that uses Unix-Domain
+ // sockets, not the network, to connect to the server.
+ ClientConnector unixDomainClientConnector = ClientConnector.forUnixDomain(unixDomainPath);
+
+ // Use Unix-Domain for HTTP/1.1.
+ HttpClientTransportOverHTTP http1Transport = new HttpClientTransportOverHTTP(unixDomainClientConnector);
+
+ // You can use Unix-Domain also for HTTP/2.
+ HTTP2Client http2Client = new HTTP2Client(unixDomainClientConnector);
+ HttpClientTransportOverHTTP2 http2Transport = new HttpClientTransportOverHTTP2(http2Client);
+
+ // You can also use UnixDomain for the dynamic transport.
+ ClientConnectionFactory.Info http1 = HttpClientConnectionFactory.HTTP11;
+ ClientConnectionFactoryOverHTTP2.HTTP2 http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
+ HttpClientTransportDynamic dynamicTransport = new HttpClientTransportDynamic(unixDomainClientConnector, http1, http2);
+
+ // Choose the transport you prefer for HttpClient, for example the dynamic transport.
+ HttpClient httpClient = new HttpClient(dynamicTransport);
+ httpClient.start();
+ // end::unixDomain[]
+ }
+}
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java
new file mode 100644
index 00000000000..8d4447db336
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java
@@ -0,0 +1,377 @@
+//
+// ========================================================================
+// 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.docs.programming.client.http2;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MetaData;
+import org.eclipse.jetty.http2.ErrorCode;
+import org.eclipse.jetty.http2.api.Session;
+import org.eclipse.jetty.http2.api.Stream;
+import org.eclipse.jetty.http2.client.HTTP2Client;
+import org.eclipse.jetty.http2.frames.DataFrame;
+import org.eclipse.jetty.http2.frames.HeadersFrame;
+import org.eclipse.jetty.http2.frames.PushPromiseFrame;
+import org.eclipse.jetty.http2.frames.ResetFrame;
+import org.eclipse.jetty.http2.frames.SettingsFrame;
+import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.util.Callback;
+
+import static java.lang.System.Logger.Level.INFO;
+
+@SuppressWarnings("unused")
+public class HTTP2ClientDocs
+{
+ public void start() throws Exception
+ {
+ // tag::start[]
+ // Instantiate HTTP2Client.
+ HTTP2Client http2Client = new HTTP2Client();
+
+ // Configure HTTP2Client, for example:
+ http2Client.setStreamIdleTimeout(15000);
+
+ // Start HTTP2Client.
+ http2Client.start();
+ // end::start[]
+ }
+
+ public void stop() throws Exception
+ {
+ HTTP2Client http2Client = new HTTP2Client();
+ http2Client.start();
+ // tag::stop[]
+ // Stop HTTP2Client.
+ http2Client.stop();
+ // end::stop[]
+ }
+
+ public void clearTextConnect() throws Exception
+ {
+ HTTP2Client http2Client = new HTTP2Client();
+ http2Client.start();
+ // tag::clearTextConnect[]
+ // Address of the server's clear-text port.
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
+
+ // Connect to the server, the CompletableFuture will be
+ // notified when the connection is succeeded (or failed).
+ CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
+
+ // Block to obtain the Session.
+ // Alternatively you can use the CompletableFuture APIs to avoid blocking.
+ Session session = sessionCF.get();
+ // end::clearTextConnect[]
+ }
+
+ public void encryptedConnect() throws Exception
+ {
+ // tag::encryptedConnect[]
+ HTTP2Client http2Client = new HTTP2Client();
+ http2Client.start();
+
+ ClientConnector connector = http2Client.getClientConnector();
+
+ // Address of the server's encrypted port.
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8443);
+
+ // Connect to the server, the CompletableFuture will be
+ // notified when the connection is succeeded (or failed).
+ CompletableFuture sessionCF = http2Client.connect(connector.getSslContextFactory(), serverAddress, new Session.Listener.Adapter());
+
+ // Block to obtain the Session.
+ // Alternatively you can use the CompletableFuture APIs to avoid blocking.
+ Session session = sessionCF.get();
+ // end::encryptedConnect[]
+ }
+
+ public void configure() throws Exception
+ {
+ HTTP2Client http2Client = new HTTP2Client();
+ http2Client.start();
+
+ // tag::configure[]
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
+ http2Client.connect(serverAddress, new Session.Listener.Adapter()
+ {
+ @Override
+ public Map onPreface(Session session)
+ {
+ Map configuration = new HashMap<>();
+
+ // Disable push from the server.
+ configuration.put(SettingsFrame.ENABLE_PUSH, 0);
+
+ // Override HTTP2Client.initialStreamRecvWindow for this session.
+ configuration.put(SettingsFrame.INITIAL_WINDOW_SIZE, 1024 * 1024);
+
+ return configuration;
+ }
+ });
+ // end::configure[]
+ }
+
+ public void newStream() throws Exception
+ {
+ HTTP2Client http2Client = new HTTP2Client();
+ http2Client.start();
+ // tag::newStream[]
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
+ CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
+ Session session = sessionCF.get();
+
+ // Configure the request headers.
+ HttpFields requestHeaders = HttpFields.build()
+ .put(HttpHeader.USER_AGENT, "Jetty HTTP2Client {version}");
+
+ // The request metadata with method, URI and headers.
+ MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders);
+
+ // The HTTP/2 HEADERS frame, with endStream=true
+ // to signal that this request has no content.
+ HeadersFrame headersFrame = new HeadersFrame(request, null, true);
+
+ // Open a Stream by sending the HEADERS frame.
+ session.newStream(headersFrame, new Stream.Listener.Adapter());
+ // end::newStream[]
+ }
+
+ public void newStreamWithData() throws Exception
+ {
+ HTTP2Client http2Client = new HTTP2Client();
+ http2Client.start();
+ // tag::newStreamWithData[]
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
+ CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
+ Session session = sessionCF.get();
+
+ // Configure the request headers.
+ HttpFields requestHeaders = HttpFields.build()
+ .put(HttpHeader.CONTENT_TYPE, "application/json");
+
+ // The request metadata with method, URI and headers.
+ MetaData.Request request = new MetaData.Request("POST", HttpURI.from("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders);
+
+ // The HTTP/2 HEADERS frame, with endStream=false to
+ // signal that there will be more frames in this stream.
+ HeadersFrame headersFrame = new HeadersFrame(request, null, false);
+
+ // Open a Stream by sending the HEADERS frame.
+ CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter());
+
+ // Block to obtain the Stream.
+ // Alternatively you can use the CompletableFuture APIs to avoid blocking.
+ Stream stream = streamCF.get();
+
+ // The request content, in two chunks.
+ String content1 = "{\"greet\": \"hello world\"}";
+ ByteBuffer buffer1 = StandardCharsets.UTF_8.encode(content1);
+ String content2 = "{\"user\": \"jetty\"}";
+ ByteBuffer buffer2 = StandardCharsets.UTF_8.encode(content2);
+
+ // Send the first DATA frame on the stream, with endStream=false
+ // to signal that there are more frames in this stream.
+ CompletableFuture dataCF1 = stream.data(new DataFrame(stream.getId(), buffer1, false));
+
+ // Only when the first chunk has been sent we can send the second,
+ // with endStream=true to signal that there are no more frames.
+ dataCF1.thenCompose(s -> s.data(new DataFrame(s.getId(), buffer2, true)));
+ // end::newStreamWithData[]
+ }
+
+ public void responseListener() throws Exception
+ {
+ HTTP2Client http2Client = new HTTP2Client();
+ http2Client.start();
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
+ CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
+ Session session = sessionCF.get();
+
+ HttpFields requestHeaders = HttpFields.build()
+ .put(HttpHeader.USER_AGENT, "Jetty HTTP2Client {version}");
+ MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders);
+ HeadersFrame headersFrame = new HeadersFrame(request, null, true);
+
+ // tag::responseListener[]
+ // Open a Stream by sending the HEADERS frame.
+ session.newStream(headersFrame, new Stream.Listener.Adapter()
+ {
+ @Override
+ public void onHeaders(Stream stream, HeadersFrame frame)
+ {
+ MetaData metaData = frame.getMetaData();
+
+ // Is this HEADERS frame the response or the trailers?
+ if (metaData.isResponse())
+ {
+ MetaData.Response response = (MetaData.Response)metaData;
+ System.getLogger("http2").log(INFO, "Received response {0}", response);
+ }
+ else
+ {
+ System.getLogger("http2").log(INFO, "Received trailers {0}", metaData.getFields());
+ }
+ }
+
+ @Override
+ public void onData(Stream stream, DataFrame frame, Callback callback)
+ {
+ // Get the content buffer.
+ ByteBuffer buffer = frame.getData();
+
+ // Consume the buffer, here - as an example - just log it.
+ System.getLogger("http2").log(INFO, "Consuming buffer {0}", buffer);
+
+ // Tell the implementation that the buffer has been consumed.
+ callback.succeeded();
+
+ // By returning from the method, implicitly tell the implementation
+ // to deliver to this method more DATA frames when they are available.
+ }
+ });
+ // end::responseListener[]
+ }
+
+ public void reset() throws Exception
+ {
+ HTTP2Client http2Client = new HTTP2Client();
+ http2Client.start();
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
+ CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
+ Session session = sessionCF.get();
+
+ HttpFields requestHeaders = HttpFields.build()
+ .put(HttpHeader.USER_AGENT, "Jetty HTTP2Client {version}");
+ MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders);
+ HeadersFrame headersFrame = new HeadersFrame(request, null, true);
+
+ // tag::reset[]
+ // Open a Stream by sending the HEADERS frame.
+ CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter()
+ {
+ @Override
+ public void onReset(Stream stream, ResetFrame frame)
+ {
+ // The server reset this stream.
+ }
+ });
+ Stream stream = streamCF.get();
+
+ // Reset this stream (for example, the user closed the application).
+ stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
+ // end::reset[]
+ }
+
+ public void push() throws Exception
+ {
+ HTTP2Client http2Client = new HTTP2Client();
+ http2Client.start();
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
+ CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
+ Session session = sessionCF.get();
+
+ HttpFields requestHeaders = HttpFields.build()
+ .put(HttpHeader.USER_AGENT, "Jetty HTTP2Client {version}");
+ MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders);
+ HeadersFrame headersFrame = new HeadersFrame(request, null, true);
+
+ // tag::push[]
+ // Open a Stream by sending the HEADERS frame.
+ CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter()
+ {
+ @Override
+ public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame)
+ {
+ // The "request" the client would make for the pushed resource.
+ MetaData.Request pushedRequest = frame.getMetaData();
+ // The pushed "request" URI.
+ HttpURI pushedURI = pushedRequest.getURI();
+ // The pushed "request" headers.
+ HttpFields pushedRequestHeaders = pushedRequest.getFields();
+
+ // If needed, retrieve the primary stream that triggered the push.
+ Stream primaryStream = pushedStream.getSession().getStream(frame.getStreamId());
+
+ // Return a Stream.Listener to listen for the pushed "response" events.
+ return new Stream.Listener.Adapter()
+ {
+ @Override
+ public void onHeaders(Stream stream, HeadersFrame frame)
+ {
+ // Handle the pushed stream "response".
+
+ MetaData metaData = frame.getMetaData();
+ if (metaData.isResponse())
+ {
+ // The pushed "response" headers.
+ HttpFields pushedResponseHeaders = metaData.getFields();
+ }
+ }
+
+ @Override
+ public void onData(Stream stream, DataFrame frame, Callback callback)
+ {
+ // Handle the pushed stream "response" content.
+
+ // The pushed stream "response" content bytes.
+ ByteBuffer buffer = frame.getData();
+ // Consume the buffer and complete the callback.
+ callback.succeeded();
+ }
+ };
+ }
+ });
+ // end::push[]
+ }
+
+ public void pushReset() throws Exception
+ {
+ HTTP2Client http2Client = new HTTP2Client();
+ http2Client.start();
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
+ CompletableFuture sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
+ Session session = sessionCF.get();
+
+ HttpFields requestHeaders = HttpFields.build()
+ .put(HttpHeader.USER_AGENT, "Jetty HTTP2Client {version}");
+ MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders);
+ HeadersFrame headersFrame = new HeadersFrame(request, null, true);
+
+ // tag::pushReset[]
+ // Open a Stream by sending the HEADERS frame.
+ CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter()
+ {
+ @Override
+ public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame)
+ {
+ // Reset the pushed stream to tell the server we are not interested.
+ pushedStream.reset(new ResetFrame(pushedStream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
+
+ // Not interested in listening to pushed response events.
+ return null;
+ }
+ });
+ // end::pushReset[]
+ }
+}
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java
new file mode 100644
index 00000000000..6af744c01e9
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http3/HTTP3ClientDocs.java
@@ -0,0 +1,356 @@
+//
+// ========================================================================
+// 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.docs.programming.client.http3;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MetaData;
+import org.eclipse.jetty.http3.api.Session;
+import org.eclipse.jetty.http3.api.Stream;
+import org.eclipse.jetty.http3.client.HTTP3Client;
+import org.eclipse.jetty.http3.frames.DataFrame;
+import org.eclipse.jetty.http3.frames.HeadersFrame;
+import org.eclipse.jetty.http3.internal.HTTP3ErrorCode;
+
+import static java.lang.System.Logger.Level.INFO;
+
+@SuppressWarnings("unused")
+public class HTTP3ClientDocs
+{
+ public void start() throws Exception
+ {
+ // tag::start[]
+ // Instantiate HTTP3Client.
+ HTTP3Client http3Client = new HTTP3Client();
+
+ // Configure HTTP3Client, for example:
+ http3Client.getHTTP3Configuration().setStreamIdleTimeout(15000);
+
+ // Start HTTP3Client.
+ http3Client.start();
+ // end::start[]
+ }
+
+ public void stop() throws Exception
+ {
+ HTTP3Client http3Client = new HTTP3Client();
+ http3Client.start();
+ // tag::stop[]
+ // Stop HTTP3Client.
+ http3Client.stop();
+ // end::stop[]
+ }
+
+ public void connect() throws Exception
+ {
+ HTTP3Client http3Client = new HTTP3Client();
+ http3Client.start();
+ // tag::connect[]
+ // Address of the server's port.
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8444);
+
+ // Connect to the server, the CompletableFuture will be
+ // notified when the connection is succeeded (or failed).
+ CompletableFuture sessionCF = http3Client.connect(serverAddress, new Session.Client.Listener() {});
+
+ // Block to obtain the Session.
+ // Alternatively you can use the CompletableFuture APIs to avoid blocking.
+ Session session = sessionCF.get();
+ // end::connect[]
+ }
+
+ public void configure() throws Exception
+ {
+ HTTP3Client http3Client = new HTTP3Client();
+ http3Client.start();
+
+ // tag::configure[]
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8444);
+ http3Client.connect(serverAddress, new Session.Client.Listener()
+ {
+ @Override
+ public Map onPreface(Session session)
+ {
+ Map configuration = new HashMap<>();
+
+ // Add here configuration settings.
+
+ return configuration;
+ }
+ });
+ // end::configure[]
+ }
+
+ public void newStream() throws Exception
+ {
+ HTTP3Client http3Client = new HTTP3Client();
+ http3Client.start();
+ // tag::newStream[]
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8444);
+ CompletableFuture sessionCF = http3Client.connect(serverAddress, new Session.Client.Listener() {});
+ Session.Client session = sessionCF.get();
+
+ // Configure the request headers.
+ HttpFields requestHeaders = HttpFields.build()
+ .put(HttpHeader.USER_AGENT, "Jetty HTTP3Client {version}");
+
+ // The request metadata with method, URI and headers.
+ MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost:8444/path"), HttpVersion.HTTP_3, requestHeaders);
+
+ // The HTTP/3 HEADERS frame, with endStream=true
+ // to signal that this request has no content.
+ HeadersFrame headersFrame = new HeadersFrame(request, true);
+
+ // Open a Stream by sending the HEADERS frame.
+ session.newRequest(headersFrame, new Stream.Client.Listener() {});
+ // end::newStream[]
+ }
+
+ public void newStreamWithData() throws Exception
+ {
+ HTTP3Client http3Client = new HTTP3Client();
+ http3Client.start();
+ // tag::newStreamWithData[]
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8444);
+ CompletableFuture sessionCF = http3Client.connect(serverAddress, new Session.Client.Listener() {});
+ Session.Client session = sessionCF.get();
+
+ // Configure the request headers.
+ HttpFields requestHeaders = HttpFields.build()
+ .put(HttpHeader.CONTENT_TYPE, "application/json");
+
+ // The request metadata with method, URI and headers.
+ MetaData.Request request = new MetaData.Request("POST", HttpURI.from("http://localhost:8444/path"), HttpVersion.HTTP_3, requestHeaders);
+
+ // The HTTP/3 HEADERS frame, with endStream=false to
+ // signal that there will be more frames in this stream.
+ HeadersFrame headersFrame = new HeadersFrame(request, false);
+
+ // Open a Stream by sending the HEADERS frame.
+ CompletableFuture streamCF = session.newRequest(headersFrame, new Stream.Client.Listener() {});
+
+ // Block to obtain the Stream.
+ // Alternatively you can use the CompletableFuture APIs to avoid blocking.
+ Stream stream = streamCF.get();
+
+ // The request content, in two chunks.
+ String content1 = "{\"greet\": \"hello world\"}";
+ ByteBuffer buffer1 = StandardCharsets.UTF_8.encode(content1);
+ String content2 = "{\"user\": \"jetty\"}";
+ ByteBuffer buffer2 = StandardCharsets.UTF_8.encode(content2);
+
+ // Send the first DATA frame on the stream, with endStream=false
+ // to signal that there are more frames in this stream.
+ CompletableFuture dataCF1 = stream.data(new DataFrame(buffer1, false));
+
+ // Only when the first chunk has been sent we can send the second,
+ // with endStream=true to signal that there are no more frames.
+ dataCF1.thenCompose(s -> s.data(new DataFrame(buffer2, true)));
+ // end::newStreamWithData[]
+ }
+
+ public void responseListener() throws Exception
+ {
+ HTTP3Client http3Client = new HTTP3Client();
+ http3Client.start();
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8444);
+ CompletableFuture sessionCF = http3Client.connect(serverAddress, new Session.Client.Listener() {});
+ Session.Client session = sessionCF.get();
+
+ HttpFields requestHeaders = HttpFields.build()
+ .put(HttpHeader.USER_AGENT, "Jetty HTTP3Client {version}");
+ MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost:8444/path"), HttpVersion.HTTP_3, requestHeaders);
+ HeadersFrame headersFrame = new HeadersFrame(request, true);
+
+ // tag::responseListener[]
+ // Open a Stream by sending the HEADERS frame.
+ session.newRequest(headersFrame, new Stream.Client.Listener()
+ {
+ @Override
+ public void onResponse(Stream.Client stream, HeadersFrame frame)
+ {
+ MetaData metaData = frame.getMetaData();
+ MetaData.Response response = (MetaData.Response)metaData;
+ System.getLogger("http3").log(INFO, "Received response {0}", response);
+ }
+
+ @Override
+ public void onDataAvailable(Stream.Client stream)
+ {
+ // Read a chunk of the content.
+ Stream.Data data = stream.readData();
+ if (data == null)
+ {
+ // No data available now, demand to be called back.
+ stream.demand();
+ }
+ else
+ {
+ // Process the content.
+ process(data.getByteBuffer());
+
+ // Notify the implementation that the content has been consumed.
+ data.complete();
+
+ if (!data.isLast())
+ {
+ // Demand to be called back.
+ stream.demand();
+ }
+ }
+ }
+ });
+ // end::responseListener[]
+ }
+
+ private void process(ByteBuffer byteBuffer)
+ {
+ }
+
+ public void reset() throws Exception
+ {
+ HTTP3Client http3Client = new HTTP3Client();
+ http3Client.start();
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8444);
+ CompletableFuture sessionCF = http3Client.connect(serverAddress, new Session.Client.Listener() {});
+ Session.Client session = sessionCF.get();
+
+ HttpFields requestHeaders = HttpFields.build()
+ .put(HttpHeader.USER_AGENT, "Jetty HTTP3Client {version}");
+ MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders);
+ HeadersFrame headersFrame = new HeadersFrame(request, true);
+
+ // tag::reset[]
+ // Open a Stream by sending the HEADERS frame.
+ CompletableFuture streamCF = session.newRequest(headersFrame, new Stream.Client.Listener()
+ {
+ @Override
+ public void onFailure(Stream.Client stream, long error, Throwable failure)
+ {
+ // The server reset this stream.
+ }
+ });
+ Stream stream = streamCF.get();
+
+ // Reset this stream (for example, the user closed the application).
+ stream.reset(HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(), new ClosedChannelException());
+ // end::reset[]
+ }
+
+ // TODO: push not yet implemented in HTTP/3
+/*
+ public void push() throws Exception
+ {
+ HTTP3Client http3Client = new HTTP3Client();
+ http3Client.start();
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8444);
+ CompletableFuture sessionCF = http3Client.connect(serverAddress, new Session.Listener.Adapter());
+ Session session = sessionCF.get();
+
+ HttpFields requestHeaders = HttpFields.build()
+ .put(HttpHeader.USER_AGENT, "Jetty HTTP3Client {version}");
+ MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders);
+ HeadersFrame headersFrame = new HeadersFrame(request, null, true);
+
+ // tag::push[]
+ // Open a Stream by sending the HEADERS frame.
+ CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter()
+ {
+ @Override
+ public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame)
+ {
+ // The "request" the client would make for the pushed resource.
+ MetaData.Request pushedRequest = frame.getMetaData();
+ // The pushed "request" URI.
+ HttpURI pushedURI = pushedRequest.getURI();
+ // The pushed "request" headers.
+ HttpFields pushedRequestHeaders = pushedRequest.getFields();
+
+ // If needed, retrieve the primary stream that triggered the push.
+ Stream primaryStream = pushedStream.getSession().getStream(frame.getStreamId());
+
+ // Return a Stream.Listener to listen for the pushed "response" events.
+ return new Adapter()
+ {
+ @Override
+ public void onHeaders(Stream stream, HeadersFrame frame)
+ {
+ // Handle the pushed stream "response".
+
+ MetaData metaData = frame.getMetaData();
+ if (metaData.isResponse())
+ {
+ // The pushed "response" headers.
+ HttpFields pushedResponseHeaders = metaData.getFields();
+ }
+ }
+
+ @Override
+ public void onData(Stream stream, DataFrame frame, Callback callback)
+ {
+ // Handle the pushed stream "response" content.
+
+ // The pushed stream "response" content bytes.
+ ByteBuffer buffer = frame.getData();
+ // Consume the buffer and complete the callback.
+ callback.succeeded();
+ }
+ };
+ }
+ });
+ // end::push[]
+ }
+
+ public void pushReset() throws Exception
+ {
+ HTTP3Client http3Client = new HTTP3Client();
+ http3Client.start();
+ SocketAddress serverAddress = new InetSocketAddress("localhost", 8444);
+ CompletableFuture sessionCF = http3Client.connect(serverAddress, new Session.Listener.Adapter());
+ Session session = sessionCF.get();
+
+ HttpFields requestHeaders = HttpFields.build()
+ .put(HttpHeader.USER_AGENT, "Jetty HTTP3Client {version}");
+ MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders);
+ HeadersFrame headersFrame = new HeadersFrame(request, null, true);
+
+ // tag::pushReset[]
+ // Open a Stream by sending the HEADERS frame.
+ CompletableFuture streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter()
+ {
+ @Override
+ public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame)
+ {
+ // Reset the pushed stream to tell the server we are not interested.
+ pushedStream.reset(new ResetFrame(pushedStream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
+
+ // Not interested in listening to pushed response events.
+ return null;
+ }
+ });
+ // end::pushReset[]
+ }
+ */
+}
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java
new file mode 100644
index 00000000000..aed23eb61e1
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java
@@ -0,0 +1,193 @@
+//
+// ========================================================================
+// 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.docs.programming.client.websocket;
+
+import java.net.HttpCookie;
+import java.net.URI;
+import java.util.concurrent.CompletableFuture;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpProxy;
+import org.eclipse.jetty.client.HttpRequest;
+import org.eclipse.jetty.client.HttpResponse;
+import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
+import org.eclipse.jetty.http2.client.HTTP2Client;
+import org.eclipse.jetty.http2.client.http.ClientConnectionFactoryOverHTTP2;
+import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.client.JettyUpgradeListener;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+
+@SuppressWarnings("unused")
+public class WebSocketClientDocs
+{
+ public void start() throws Exception
+ {
+ // tag::start[]
+ // Instantiate WebSocketClient.
+ WebSocketClient webSocketClient = new WebSocketClient();
+
+ // Configure WebSocketClient, for example:
+ webSocketClient.setMaxTextMessageSize(8 * 1024);
+
+ // Start WebSocketClient.
+ webSocketClient.start();
+ // end::start[]
+ }
+
+ public void startWithHttpClient() throws Exception
+ {
+ // tag::startWithHttpClient[]
+ // Instantiate and configure HttpClient.
+ HttpClient httpClient = new HttpClient();
+ // For example, configure a proxy.
+ httpClient.getProxyConfiguration().addProxy(new HttpProxy("localhost", 8888));
+
+ // Instantiate WebSocketClient, passing HttpClient to the constructor.
+ WebSocketClient webSocketClient = new WebSocketClient(httpClient);
+ // Configure WebSocketClient, for example:
+ webSocketClient.setMaxTextMessageSize(8 * 1024);
+
+ // Start WebSocketClient; this implicitly starts also HttpClient.
+ webSocketClient.start();
+ // end::startWithHttpClient[]
+ }
+
+ public void stop() throws Exception
+ {
+ WebSocketClient webSocketClient = new WebSocketClient();
+ webSocketClient.start();
+ // tag::stop[]
+ // Stop WebSocketClient.
+ // Use LifeCycle.stop(...) to rethrow checked exceptions as unchecked.
+ new Thread(() -> LifeCycle.stop(webSocketClient)).start();
+ // end::stop[]
+ }
+
+ public void connectHTTP11() throws Exception
+ {
+ // tag::connectHTTP11[]
+ // Use a standard, HTTP/1.1, HttpClient.
+ HttpClient httpClient = new HttpClient();
+
+ // Create and start WebSocketClient.
+ WebSocketClient webSocketClient = new WebSocketClient(httpClient);
+ webSocketClient.start();
+
+ // The client-side WebSocket EndPoint that
+ // receives WebSocket messages from the server.
+ ClientEndPoint clientEndPoint = new ClientEndPoint();
+ // The server URI to connect to.
+ URI serverURI = URI.create("ws://domain.com/path");
+
+ // Connect the client EndPoint to the server.
+ CompletableFuture clientSessionPromise = webSocketClient.connect(clientEndPoint, serverURI);
+ // end::connectHTTP11[]
+ }
+
+ public void connectHTTP2() throws Exception
+ {
+ // tag::connectHTTP2[]
+ // Use the HTTP/2 transport for HttpClient.
+ HTTP2Client http2Client = new HTTP2Client();
+ HttpClient httpClient = new HttpClient(new HttpClientTransportOverHTTP2(http2Client));
+
+ // Create and start WebSocketClient.
+ WebSocketClient webSocketClient = new WebSocketClient(httpClient);
+ webSocketClient.start();
+
+ // The client-side WebSocket EndPoint that
+ // receives WebSocket messages from the server.
+ ClientEndPoint clientEndPoint = new ClientEndPoint();
+ // The server URI to connect to.
+ URI serverURI = URI.create("wss://domain.com/path");
+
+ // Connect the client EndPoint to the server.
+ CompletableFuture clientSessionPromise = webSocketClient.connect(clientEndPoint, serverURI);
+ // end::connectHTTP2[]
+ }
+
+ public void connectHTTP2Dynamic() throws Exception
+ {
+ // tag::connectHTTP2Dynamic[]
+ // Use the dynamic HTTP/2 transport for HttpClient.
+ HTTP2Client http2Client = new HTTP2Client();
+ HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client)));
+
+ // Create and start WebSocketClient.
+ WebSocketClient webSocketClient = new WebSocketClient(httpClient);
+ webSocketClient.start();
+
+ ClientEndPoint clientEndPoint = new ClientEndPoint();
+ URI serverURI = URI.create("wss://domain.com/path");
+
+ // Connect the client EndPoint to the server.
+ CompletableFuture clientSessionPromise = webSocketClient.connect(clientEndPoint, serverURI);
+ // end::connectHTTP2Dynamic[]
+ }
+
+ public void customHTTPRequest() throws Exception
+ {
+ WebSocketClient webSocketClient = new WebSocketClient(new HttpClient());
+ webSocketClient.start();
+
+ // tag::customHTTPRequest[]
+ ClientEndPoint clientEndPoint = new ClientEndPoint();
+ URI serverURI = URI.create("ws://domain.com/path");
+
+ // Create a custom HTTP request.
+ ClientUpgradeRequest customRequest = new ClientUpgradeRequest();
+ // Specify a cookie.
+ customRequest.getCookies().add(new HttpCookie("name", "value"));
+ // Specify a custom header.
+ customRequest.setHeader("X-Token", "0123456789ABCDEF");
+ // Specify a custom sub-protocol.
+ customRequest.setSubProtocols("chat");
+
+ // Connect the client EndPoint to the server with a custom HTTP request.
+ CompletableFuture clientSessionPromise = webSocketClient.connect(clientEndPoint, serverURI, customRequest);
+ // end::customHTTPRequest[]
+ }
+
+ public void inspectHTTPResponse() throws Exception
+ {
+ WebSocketClient webSocketClient = new WebSocketClient(new HttpClient());
+ webSocketClient.start();
+
+ // tag::inspectHTTPResponse[]
+ ClientEndPoint clientEndPoint = new ClientEndPoint();
+ URI serverURI = URI.create("ws://domain.com/path");
+
+ // The listener to inspect the HTTP response.
+ JettyUpgradeListener listener = new JettyUpgradeListener()
+ {
+ @Override
+ public void onHandshakeResponse(HttpRequest request, HttpResponse response)
+ {
+ // Inspect the HTTP response here.
+ }
+ };
+
+ // Connect the client EndPoint to the server with a custom HTTP request.
+ CompletableFuture clientSessionPromise = webSocketClient.connect(clientEndPoint, serverURI, null, listener);
+ // end::inspectHTTPResponse[]
+ }
+
+ @SuppressWarnings("InnerClassMayBeStatic")
+ public class ClientEndPoint
+ {
+ }
+}
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/ServerDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/ServerDocs.java
new file mode 100644
index 00000000000..a109be5ed6d
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/ServerDocs.java
@@ -0,0 +1,335 @@
+//
+// ========================================================================
+// 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.docs.programming.server;
+
+import java.nio.ByteBuffer;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.http.CookieCompliance;
+import org.eclipse.jetty.http.HttpCompliance;
+import org.eclipse.jetty.http.UriCompliance;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.DetectorConnectionFactory;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.ajax.AsyncJSON;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+@SuppressWarnings("unused")
+public class ServerDocs
+{
+ public void http() throws Exception
+ {
+ // tag::http[]
+ // Create the HTTP/1.1 ConnectionFactory.
+ HttpConnectionFactory http = new HttpConnectionFactory();
+
+ Server server = new Server();
+
+ // Create the connector with the ConnectionFactory.
+ ServerConnector connector = new ServerConnector(server, http);
+ connector.setPort(8080);
+
+ server.addConnector(connector);
+ server.start();
+ // end::http[]
+ }
+
+ public void httpUnix() throws Exception
+ {
+ // tag::httpUnix[]
+ // Create the HTTP/1.1 ConnectionFactory.
+ HttpConnectionFactory http = new HttpConnectionFactory();
+
+ Server server = new Server();
+
+ // Create the connector with the ConnectionFactory.
+ UnixDomainServerConnector connector = new UnixDomainServerConnector(server, http);
+ connector.setUnixDomainPath(Path.of("/tmp/jetty.sock"));
+
+ server.addConnector(connector);
+ server.start();
+ // end::httpUnix[]
+ }
+
+ public void tlsHttp() throws Exception
+ {
+ // tag::tlsHttp[]
+ // Create the HTTP/1.1 ConnectionFactory.
+ HttpConnectionFactory http = new HttpConnectionFactory();
+
+ // Create and configure the TLS context factory.
+ SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+ sslContextFactory.setKeyStorePath("/path/to/keystore.p12");
+ sslContextFactory.setKeyStorePassword("secret");
+
+ // Create the TLS ConnectionFactory,
+ // setting HTTP/1.1 as the wrapped protocol.
+ SslConnectionFactory tls = new SslConnectionFactory(sslContextFactory, http.getProtocol());
+
+ Server server = new Server();
+
+ // Create the connector with both ConnectionFactories.
+ ServerConnector connector = new ServerConnector(server, tls, http);
+ connector.setPort(8443);
+
+ server.addConnector(connector);
+ server.start();
+ // end::tlsHttp[]
+ }
+
+ public void detector() throws Exception
+ {
+ // tag::detector[]
+ // Create the HTTP/1.1 ConnectionFactory.
+ HttpConnectionFactory http = new HttpConnectionFactory();
+
+ // Create and configure the TLS context factory.
+ SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+ sslContextFactory.setKeyStorePath("/path/to/keystore.p12");
+ sslContextFactory.setKeyStorePassword("secret");
+
+ // Create the TLS ConnectionFactory,
+ // setting HTTP/1.1 as the wrapped protocol.
+ SslConnectionFactory tls = new SslConnectionFactory(sslContextFactory, http.getProtocol());
+
+ Server server = new Server();
+
+ // Create the detector ConnectionFactory to
+ // detect whether the initial bytes are TLS.
+ DetectorConnectionFactory tlsDetector = new DetectorConnectionFactory(tls); // <1>
+
+ // Create the connector with both ConnectionFactories.
+ ServerConnector connector = new ServerConnector(server, tlsDetector, http); // <2>
+ connector.setPort(8181);
+
+ server.addConnector(connector);
+ server.start();
+ // end::detector[]
+ }
+
+ // tag::jsonHttpConnectionFactory[]
+ public class JSONHTTPConnectionFactory extends AbstractConnectionFactory
+ {
+ public JSONHTTPConnectionFactory()
+ {
+ super("JSONHTTP");
+ }
+
+ @Override
+ public Connection newConnection(Connector connector, EndPoint endPoint)
+ {
+ JSONHTTPConnection connection = new JSONHTTPConnection(endPoint, connector.getExecutor());
+ // Call configure() to apply configurations common to all connections.
+ return configure(connection, connector, endPoint);
+ }
+ }
+ // end::jsonHttpConnectionFactory[]
+
+ @SuppressWarnings("InnerClassMayBeStatic")
+ // tag::jsonHttpConnection[]
+ public class JSONHTTPConnection extends AbstractConnection
+ {
+ // The asynchronous JSON parser.
+ private final AsyncJSON parser = new AsyncJSON.Factory().newAsyncJSON();
+ private final IteratingCallback callback = new JSONHTTPIteratingCallback();
+
+ public JSONHTTPConnection(EndPoint endPoint, Executor executor)
+ {
+ super(endPoint, executor);
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+
+ // Declare interest in being called back when
+ // there are bytes to read from the network.
+ fillInterested();
+ }
+
+ @Override
+ public void onFillable()
+ {
+ callback.iterate();
+ }
+
+ private class JSONHTTPIteratingCallback extends IteratingCallback
+ {
+ private ByteBuffer buffer;
+
+ @Override
+ protected Action process() throws Throwable
+ {
+ if (buffer == null)
+ buffer = BufferUtil.allocate(getInputBufferSize(), true);
+
+ while (true)
+ {
+ int filled = getEndPoint().fill(buffer);
+ if (filled > 0)
+ {
+ boolean parsed = parser.parse(buffer);
+ if (parsed)
+ {
+ Map request = parser.complete();
+
+ // Allow applications to process the request.
+ invokeApplication(request, this);
+
+ // Signal that the iteration should resume when
+ // the application completed the request processing.
+ return Action.SCHEDULED;
+ }
+ else
+ {
+ // Did not receive enough JSON bytes,
+ // loop around to try to read more.
+ }
+ }
+ else if (filled == 0)
+ {
+ // We don't need the buffer anymore, so
+ // don't keep it around while we are idle.
+ buffer = null;
+
+ // No more bytes to read, declare
+ // again interest for fill events.
+ fillInterested();
+
+ // Signal that the iteration is now IDLE.
+ return Action.IDLE;
+ }
+ else
+ {
+ // The other peer closed the connection,
+ // the iteration completed successfully.
+ return Action.SUCCEEDED;
+ }
+ }
+ }
+
+ @Override
+ protected void onCompleteSuccess()
+ {
+ getEndPoint().close();
+ }
+
+ @Override
+ protected void onCompleteFailure(Throwable cause)
+ {
+ getEndPoint().close(cause);
+ }
+ }
+ }
+ // end::jsonHttpConnection[]
+
+ private void invokeApplication(Map request, Callback callback)
+ {
+ }
+
+ // tag::jsonHttpAPI[]
+ class JSONHTTPRequest
+ {
+ // Request APIs
+ }
+
+ class JSONHTTPResponse
+ {
+ // Response APIs
+ }
+
+ interface JSONHTTPService
+ {
+ void service(JSONHTTPRequest request, JSONHTTPResponse response, Callback callback);
+ }
+ // end::jsonHttpAPI[]
+
+ public void httpCompliance()
+ {
+ // tag::httpCompliance[]
+ HttpConfiguration httpConfiguration = new HttpConfiguration();
+ httpConfiguration.setHttpCompliance(HttpCompliance.RFC7230);
+ // end::httpCompliance[]
+ }
+
+ public void httpComplianceCustom()
+ {
+ // tag::httpComplianceCustom[]
+ HttpConfiguration httpConfiguration = new HttpConfiguration();
+
+ // RFC7230 compliance, but allow Violation.MULTIPLE_CONTENT_LENGTHS.
+ HttpCompliance customHttpCompliance = HttpCompliance.from("RFC7230,MULTIPLE_CONTENT_LENGTHS");
+
+ httpConfiguration.setHttpCompliance(customHttpCompliance);
+ // end::httpComplianceCustom[]
+ }
+
+ public void uriCompliance()
+ {
+ // tag::uriCompliance[]
+ HttpConfiguration httpConfiguration = new HttpConfiguration();
+ httpConfiguration.setUriCompliance(UriCompliance.RFC3986);
+ // end::uriCompliance[]
+ }
+
+ public void uriComplianceCustom()
+ {
+ // tag::uriComplianceCustom[]
+ HttpConfiguration httpConfiguration = new HttpConfiguration();
+
+ // RFC3986 compliance, but enforce Violation.AMBIGUOUS_PATH_SEPARATOR.
+ UriCompliance customUriCompliance = UriCompliance.from("RFC3986,-AMBIGUOUS_PATH_SEPARATOR");
+
+ httpConfiguration.setUriCompliance(customUriCompliance);
+ // end::uriComplianceCustom[]
+ }
+
+ public void cookieCompliance()
+ {
+ // tag::cookieCompliance[]
+ HttpConfiguration httpConfiguration = new HttpConfiguration();
+ httpConfiguration.setRequestCookieCompliance(CookieCompliance.RFC6265);
+ httpConfiguration.setResponseCookieCompliance(CookieCompliance.RFC6265);
+ // end::cookieCompliance[]
+ }
+
+ public void cookieComplianceCustom()
+ {
+ // tag::cookieComplianceCustom[]
+ HttpConfiguration httpConfiguration = new HttpConfiguration();
+
+ // RFC6265 compliance, but enforce Violation.RESERVED_NAMES_NOT_DOLLAR_PREFIXED.
+ CookieCompliance customUriCompliance = CookieCompliance.from("RFC6265,-RESERVED_NAMES_NOT_DOLLAR_PREFIXED");
+ httpConfiguration.setRequestCookieCompliance(customUriCompliance);
+
+ httpConfiguration.setResponseCookieCompliance(CookieCompliance.RFC6265);
+
+ // end::cookieComplianceCustom[]
+ }
+}
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java
new file mode 100644
index 00000000000..7fe7431075a
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java
@@ -0,0 +1,1076 @@
+//
+// ========================================================================
+// 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.docs.programming.server.http;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.security.Security;
+import java.util.EnumSet;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.conscrypt.OpenSSLProvider;
+import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
+import org.eclipse.jetty.http.HttpCompliance;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
+import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
+import org.eclipse.jetty.http3.server.HTTP3ServerConnectionFactory;
+import org.eclipse.jetty.http3.server.HTTP3ServerConnector;
+import org.eclipse.jetty.rewrite.handler.CompactPathRule;
+import org.eclipse.jetty.rewrite.handler.RedirectRegexRule;
+import org.eclipse.jetty.rewrite.handler.RewriteHandler;
+import org.eclipse.jetty.rewrite.handler.RewriteRegexRule;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.CustomRequestLog;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.ProxyConnectionFactory;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.RequestLogWriter;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.Slf4jRequestLogWriter;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.server.handler.RequestLogHandler;
+import org.eclipse.jetty.server.handler.ResourceHandler;
+import org.eclipse.jetty.server.handler.SecuredRedirectHandler;
+import org.eclipse.jetty.server.handler.StatisticsHandler;
+import org.eclipse.jetty.server.handler.gzip.GzipHandler;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.servlets.CrossOriginFilter;
+import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.NanoTime;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceCollection;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+import static java.lang.System.Logger.Level.INFO;
+
+@SuppressWarnings("unused")
+public class HTTPServerDocs
+{
+ public void simple() throws Exception
+ {
+ // tag::simple[]
+ // Create and configure a ThreadPool.
+ QueuedThreadPool threadPool = new QueuedThreadPool();
+ threadPool.setName("server");
+
+ // Create a Server instance.
+ Server server = new Server(threadPool);
+
+ // Create a ServerConnector to accept connections from clients.
+ Connector connector = new ServerConnector(server);
+
+ // Add the Connector to the Server
+ server.addConnector(connector);
+
+ // Set a simple Handler to handle requests/responses.
+ server.setHandler(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
+ {
+ // Mark the request as handled so that it
+ // will not be processed by other handlers.
+ jettyRequest.setHandled(true);
+ }
+ });
+
+ // Start the Server so it starts accepting connections from clients.
+ server.start();
+ // end::simple[]
+ }
+
+ public void httpChannelListener() throws Exception
+ {
+ // tag::httpChannelListener[]
+ class TimingHttpChannelListener implements HttpChannel.Listener
+ {
+ private final ConcurrentMap times = new ConcurrentHashMap<>();
+
+ @Override
+ public void onRequestBegin(Request request)
+ {
+ times.put(request, NanoTime.now());
+ }
+
+ @Override
+ public void onComplete(Request request)
+ {
+ long begin = times.remove(request);
+ long elapsed = NanoTime.since(begin);
+ System.getLogger("timing").log(INFO, "Request {0} took {1} ns", request, elapsed);
+ }
+ }
+
+ Server server = new Server();
+
+ Connector connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ // Add the HttpChannel.Listener as bean to the connector.
+ connector.addBean(new TimingHttpChannelListener());
+
+ // Set a simple Handler to handle requests/responses.
+ server.setHandler(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
+ {
+ jettyRequest.setHandled(true);
+ }
+ });
+
+ server.start();
+ // end::httpChannelListener[]
+ }
+
+ public void serverRequestLogSLF4J()
+ {
+ // tag::serverRequestLogSLF4J[]
+ Server server = new Server();
+
+ // Sets the RequestLog to log to an SLF4J logger named "org.eclipse.jetty.server.RequestLog" at INFO level.
+ server.setRequestLog(new CustomRequestLog(new Slf4jRequestLogWriter(), CustomRequestLog.EXTENDED_NCSA_FORMAT));
+ // end::serverRequestLogSLF4J[]
+ }
+
+ public void serverRequestLogFile()
+ {
+ // tag::serverRequestLogFile[]
+ Server server = new Server();
+
+ // Use a file name with the pattern 'yyyy_MM_dd' so rolled over files retain their date.
+ RequestLogWriter logWriter = new RequestLogWriter("/var/log/yyyy_MM_dd.jetty.request.log");
+ // Retain rolled over files for 2 weeks.
+ logWriter.setRetainDays(14);
+ // Log times are in the current time zone.
+ logWriter.setTimeZone(TimeZone.getDefault().getID());
+
+ // Set the RequestLog to log to the given file, rolling over at midnight.
+ server.setRequestLog(new CustomRequestLog(logWriter, CustomRequestLog.EXTENDED_NCSA_FORMAT));
+ // end::serverRequestLogFile[]
+ }
+
+ public void contextRequestLog()
+ {
+ // tag::contextRequestLog[]
+ Server server = new Server();
+
+ // Create a first ServletContextHandler for your main application.
+ ServletContextHandler mainContext = new ServletContextHandler();
+ mainContext.setContextPath("/main");
+
+ // Create a RequestLogHandler to log requests for your main application.
+ RequestLogHandler requestLogHandler = new RequestLogHandler();
+ requestLogHandler.setRequestLog(new CustomRequestLog());
+ // Wrap the main application with the request log handler.
+ requestLogHandler.setHandler(mainContext);
+
+ // Create a second ServletContextHandler for your other application.
+ // No request logging for this application.
+ ServletContextHandler otherContext = new ServletContextHandler();
+ mainContext.setContextPath("/other");
+
+ server.setHandler(new HandlerList(requestLogHandler, otherContext));
+ // end::contextRequestLog[]
+ }
+
+ public void configureConnector() throws Exception
+ {
+ // tag::configureConnector[]
+ Server server = new Server();
+
+ // The number of acceptor threads.
+ int acceptors = 1;
+
+ // The number of selectors.
+ int selectors = 1;
+
+ // Create a ServerConnector instance.
+ ServerConnector connector = new ServerConnector(server, acceptors, selectors, new HttpConnectionFactory());
+
+ // Configure TCP/IP parameters.
+
+ // The port to listen to.
+ connector.setPort(8080);
+ // The address to bind to.
+ connector.setHost("127.0.0.1");
+
+ // The TCP accept queue size.
+ connector.setAcceptQueueSize(128);
+
+ server.addConnector(connector);
+ server.start();
+ // end::configureConnector[]
+ }
+
+ public void configureConnectorUnix() throws Exception
+ {
+ // tag::configureConnectorUnix[]
+ Server server = new Server();
+
+ // The number of acceptor threads.
+ int acceptors = 1;
+
+ // The number of selectors.
+ int selectors = 1;
+
+ // Create a ServerConnector instance.
+ UnixDomainServerConnector connector = new UnixDomainServerConnector(server, acceptors, selectors, new HttpConnectionFactory());
+
+ // Configure Unix-Domain parameters.
+
+ // The Unix-Domain path to listen to.
+ connector.setUnixDomainPath(Path.of("/tmp/jetty.sock"));
+
+ // The TCP accept queue size.
+ connector.setAcceptQueueSize(128);
+
+ server.addConnector(connector);
+ server.start();
+ // end::configureConnectorUnix[]
+ }
+
+ public void configureConnectors() throws Exception
+ {
+ // tag::configureConnectors[]
+ Server server = new Server();
+
+ // Create a ServerConnector instance on port 8080.
+ ServerConnector connector1 = new ServerConnector(server, 1, 1, new HttpConnectionFactory());
+ connector1.setPort(8080);
+ server.addConnector(connector1);
+
+ // Create another ServerConnector instance on port 9090,
+ // for example with a different HTTP configuration.
+ HttpConfiguration httpConfig2 = new HttpConfiguration();
+ httpConfig2.setHttpCompliance(HttpCompliance.LEGACY);
+ ServerConnector connector2 = new ServerConnector(server, 1, 1, new HttpConnectionFactory(httpConfig2));
+ connector2.setPort(9090);
+ server.addConnector(connector2);
+
+ server.start();
+ // end::configureConnectors[]
+ }
+
+ public void http11() throws Exception
+ {
+ // tag::http11[]
+ Server server = new Server();
+
+ // The HTTP configuration object.
+ HttpConfiguration httpConfig = new HttpConfiguration();
+ // Configure the HTTP support, for example:
+ httpConfig.setSendServerVersion(false);
+
+ // The ConnectionFactory for HTTP/1.1.
+ HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig);
+
+ // Create the ServerConnector.
+ ServerConnector connector = new ServerConnector(server, http11);
+ connector.setPort(8080);
+
+ server.addConnector(connector);
+ server.start();
+ // end::http11[]
+ }
+
+ public void proxyHTTP() throws Exception
+ {
+ // tag::proxyHTTP[]
+ Server server = new Server();
+
+ // The HTTP configuration object.
+ HttpConfiguration httpConfig = new HttpConfiguration();
+ // Configure the HTTP support, for example:
+ httpConfig.setSendServerVersion(false);
+
+ // The ConnectionFactory for HTTP/1.1.
+ HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig);
+
+ // The ConnectionFactory for the PROXY protocol.
+ ProxyConnectionFactory proxy = new ProxyConnectionFactory(http11.getProtocol());
+
+ // Create the ServerConnector.
+ ServerConnector connector = new ServerConnector(server, proxy, http11);
+ connector.setPort(8080);
+
+ server.addConnector(connector);
+ server.start();
+ // end::proxyHTTP[]
+ }
+
+ public void proxyHTTPUnix() throws Exception
+ {
+ // tag::proxyHTTPUnix[]
+ Server server = new Server();
+
+ // The HTTP configuration object.
+ HttpConfiguration httpConfig = new HttpConfiguration();
+ // Configure the HTTP support, for example:
+ httpConfig.setSendServerVersion(false);
+
+ // The ConnectionFactory for HTTP/1.1.
+ HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig);
+
+ // The ConnectionFactory for the PROXY protocol.
+ ProxyConnectionFactory proxy = new ProxyConnectionFactory(http11.getProtocol());
+
+ // Create the ServerConnector.
+ UnixDomainServerConnector connector = new UnixDomainServerConnector(server, proxy, http11);
+ connector.setUnixDomainPath(Path.of("/tmp/jetty.sock"));
+
+ server.addConnector(connector);
+ server.start();
+ // end::proxyHTTPUnix[]
+ }
+
+ public void tlsHttp11() throws Exception
+ {
+ // tag::tlsHttp11[]
+ Server server = new Server();
+
+ // The HTTP configuration object.
+ HttpConfiguration httpConfig = new HttpConfiguration();
+ // Add the SecureRequestCustomizer because we are using TLS.
+ httpConfig.addCustomizer(new SecureRequestCustomizer());
+
+ // The ConnectionFactory for HTTP/1.1.
+ HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig);
+
+ // Configure the SslContextFactory with the keyStore information.
+ SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+ sslContextFactory.setKeyStorePath("/path/to/keystore");
+ sslContextFactory.setKeyStorePassword("secret");
+
+ // The ConnectionFactory for TLS.
+ SslConnectionFactory tls = new SslConnectionFactory(sslContextFactory, http11.getProtocol());
+
+ // The ServerConnector instance.
+ ServerConnector connector = new ServerConnector(server, tls, http11);
+ connector.setPort(8443);
+
+ server.addConnector(connector);
+ server.start();
+ // end::tlsHttp11[]
+ }
+
+ public void http11H2C() throws Exception
+ {
+ // tag::http11H2C[]
+ Server server = new Server();
+
+ // The HTTP configuration object.
+ HttpConfiguration httpConfig = new HttpConfiguration();
+
+ // The ConnectionFactory for HTTP/1.1.
+ HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig);
+
+ // The ConnectionFactory for clear-text HTTP/2.
+ HTTP2CServerConnectionFactory h2c = new HTTP2CServerConnectionFactory(httpConfig);
+
+ // The ServerConnector instance.
+ ServerConnector connector = new ServerConnector(server, http11, h2c);
+ connector.setPort(8080);
+
+ server.addConnector(connector);
+ server.start();
+ // end::http11H2C[]
+ }
+
+ public void tlsALPNHTTP() throws Exception
+ {
+ // tag::tlsALPNHTTP[]
+ Server server = new Server();
+
+ // The HTTP configuration object.
+ HttpConfiguration httpConfig = new HttpConfiguration();
+ // Add the SecureRequestCustomizer because we are using TLS.
+ httpConfig.addCustomizer(new SecureRequestCustomizer());
+
+ // The ConnectionFactory for HTTP/1.1.
+ HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig);
+
+ // The ConnectionFactory for HTTP/2.
+ HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(httpConfig);
+
+ // The ALPN ConnectionFactory.
+ ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
+ // The default protocol to use in case there is no negotiation.
+ alpn.setDefaultProtocol(http11.getProtocol());
+
+ // Configure the SslContextFactory with the keyStore information.
+ SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+ sslContextFactory.setKeyStorePath("/path/to/keystore");
+ sslContextFactory.setKeyStorePassword("secret");
+
+ // The ConnectionFactory for TLS.
+ SslConnectionFactory tls = new SslConnectionFactory(sslContextFactory, alpn.getProtocol());
+
+ // The ServerConnector instance.
+ ServerConnector connector = new ServerConnector(server, tls, alpn, h2, http11);
+ connector.setPort(8443);
+
+ server.addConnector(connector);
+ server.start();
+ // end::tlsALPNHTTP[]
+ }
+
+ public void h3() throws Exception
+ {
+ // tag::h3[]
+ Server server = new Server();
+
+ SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+ sslContextFactory.setKeyStorePath("/path/to/keystore");
+ sslContextFactory.setKeyStorePassword("secret");
+
+ HttpConfiguration httpConfig = new HttpConfiguration();
+ httpConfig.addCustomizer(new SecureRequestCustomizer());
+
+ // Create and configure the HTTP/3 connector.
+ HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactory, new HTTP3ServerConnectionFactory(httpConfig));
+ connector.setPort(843);
+ server.addConnector(connector);
+
+ server.start();
+ // end::h3[]
+ }
+
+ public void conscrypt()
+ {
+ // tag::conscrypt[]
+ // Configure the JDK with the Conscrypt provider.
+ Security.addProvider(new OpenSSLProvider());
+
+ SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+ sslContextFactory.setKeyStorePath("/path/to/keystore");
+ sslContextFactory.setKeyStorePassword("secret");
+ // Configure Jetty's SslContextFactory to use Conscrypt.
+ sslContextFactory.setProvider("Conscrypt");
+ // end::conscrypt[]
+ }
+
+ public void handlerTree()
+ {
+ class LoggingHandler extends AbstractHandler
+ {
+ @Override
+ public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
+ {
+ }
+ }
+
+ class App1Handler extends AbstractHandler
+ {
+ @Override
+ public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
+ {
+ }
+ }
+
+ class App2Handler extends HandlerWrapper
+ {
+ @Override
+ public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
+ {
+ }
+ }
+
+ // tag::handlerTree[]
+ // Create a Server instance.
+ Server server = new Server();
+
+ HandlerCollection collection = new HandlerCollection();
+ // Link the root Handler with the Server.
+ server.setHandler(collection);
+
+ HandlerList list = new HandlerList();
+ collection.addHandler(list);
+ collection.addHandler(new LoggingHandler());
+
+ list.addHandler(new App1Handler());
+ HandlerWrapper wrapper = new HandlerWrapper();
+ list.addHandler(wrapper);
+
+ wrapper.setHandler(new App2Handler());
+ // end::handlerTree[]
+ }
+
+ public void handlerAPI()
+ {
+ class MyHandler extends AbstractHandler
+ {
+ @Override
+ // tag::handlerAPI[]
+ public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
+ {
+ }
+ // end::handlerAPI[]
+ }
+ }
+
+ public void handlerHello() throws Exception
+ {
+ // tag::handlerHello[]
+ class HelloWorldHandler extends AbstractHandler
+ {
+ @Override
+ public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ // Mark the request as handled by this Handler.
+ jettyRequest.setHandled(true);
+
+ response.setStatus(200);
+ response.setContentType("text/html; charset=UTF-8");
+
+ // Write a Hello World response.
+ response.getWriter().print("" +
+ "" +
+ "" +
+ "" +
+ " Jetty Hello World Handler " +
+ "" +
+ "" +
+ " Hello World
" +
+ "" +
+ "" +
+ "");
+ }
+ }
+
+ Server server = new Server();
+ Connector connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ // Set the Hello World Handler.
+ server.setHandler(new HelloWorldHandler());
+
+ server.start();
+ // end::handlerHello[]
+ }
+
+ public void handlerFilter() throws Exception
+ {
+ class HelloWorldHandler extends AbstractHandler
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ {
+ }
+ }
+
+ // tag::handlerFilter[]
+ class FilterHandler extends HandlerWrapper
+ {
+ @Override
+ public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ String path = request.getRequestURI();
+ if (path.startsWith("/old_path/"))
+ {
+ // Rewrite old paths to new paths.
+ HttpURI uri = jettyRequest.getHttpURI();
+ String newPath = "/new_path/" + path.substring("/old_path/".length());
+ HttpURI newURI = HttpURI.build(uri).path(newPath);
+ // Modify the request object.
+ jettyRequest.setHttpURI(newURI);
+ }
+
+ // This Handler is not handling the request, so
+ // it does not call jettyRequest.setHandled(true).
+
+ // Forward to the next Handler.
+ super.handle(target, jettyRequest, request, response);
+ }
+ }
+
+ Server server = new Server();
+ Connector connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ // Link the Handlers.
+ FilterHandler filter = new FilterHandler();
+ filter.setHandler(new HelloWorldHandler());
+ server.setHandler(filter);
+
+ server.start();
+ // end::handlerFilter[]
+ }
+
+ public void contextHandler() throws Exception
+ {
+ // tag::contextHandler[]
+ class ShopHandler extends AbstractHandler
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ {
+ baseRequest.setHandled(true);
+ // Implement the shop.
+ }
+ }
+
+ Server server = new Server();
+ Connector connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ // Create a ContextHandler with contextPath.
+ ContextHandler context = new ContextHandler();
+ context.setContextPath("/shop");
+ context.setHandler(new ShopHandler());
+
+ // Link the context to the server.
+ server.setHandler(context);
+
+ server.start();
+ // end::contextHandler[]
+ }
+
+ public void contextHandlerCollection() throws Exception
+ {
+ // tag::contextHandlerCollection[]
+ class ShopHandler extends AbstractHandler
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ {
+ baseRequest.setHandled(true);
+ // Implement the shop.
+ }
+ }
+
+ class RESTHandler extends AbstractHandler
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ {
+ baseRequest.setHandled(true);
+ // Implement the REST APIs.
+ }
+ }
+
+ Server server = new Server();
+ Connector connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ // Create a ContextHandlerCollection to hold contexts.
+ ContextHandlerCollection contextCollection = new ContextHandlerCollection();
+ // Link the ContextHandlerCollection to the Server.
+ server.setHandler(contextCollection);
+
+ // Create the context for the shop web application.
+ ContextHandler shopContext = new ContextHandler("/shop");
+ shopContext.setHandler(new ShopHandler());
+ // Add it to ContextHandlerCollection.
+ contextCollection.addHandler(shopContext);
+
+ server.start();
+
+ // Create the context for the API web application.
+ ContextHandler apiContext = new ContextHandler("/api");
+ apiContext.setHandler(new RESTHandler());
+ // Web applications can be deployed after the Server is started.
+ contextCollection.deployHandler(apiContext, Callback.NOOP);
+ // end::contextHandlerCollection[]
+ }
+
+ public void servletContextHandler() throws Exception
+ {
+ // tag::servletContextHandler[]
+ class ShopCartServlet extends HttpServlet
+ {
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response)
+ {
+ // Implement the shop cart functionality.
+ }
+ }
+
+ Server server = new Server();
+ Connector connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ // Create a ServletContextHandler with contextPath.
+ ServletContextHandler context = new ServletContextHandler();
+ context.setContextPath("/shop");
+
+ // Add the Servlet implementing the cart functionality to the context.
+ ServletHolder servletHolder = context.addServlet(ShopCartServlet.class, "/cart/*");
+ // Configure the Servlet with init-parameters.
+ servletHolder.setInitParameter("maxItems", "128");
+
+ // Add the CrossOriginFilter to protect from CSRF attacks.
+ FilterHolder filterHolder = context.addFilter(CrossOriginFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
+ // Configure the filter.
+ filterHolder.setAsyncSupported(true);
+
+ // Link the context to the server.
+ server.setHandler(context);
+
+ server.start();
+ // end::servletContextHandler[]
+ }
+
+ public void webAppContextHandler() throws Exception
+ {
+ // tag::webAppContextHandler[]
+ Server server = new Server();
+ Connector connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ // Create a WebAppContext.
+ WebAppContext context = new WebAppContext();
+ // Configure the path of the packaged web application (file or directory).
+ context.setWar("/path/to/webapp.war");
+ // Configure the contextPath.
+ context.setContextPath("/app");
+
+ // Link the context to the server.
+ server.setHandler(context);
+
+ server.start();
+ // end::webAppContextHandler[]
+ }
+
+ public void resourceHandler() throws Exception
+ {
+ // tag::resourceHandler[]
+ Server server = new Server();
+ Connector connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ // Create and configure a ResourceHandler.
+ ResourceHandler handler = new ResourceHandler();
+ // Configure the directory where static resources are located.
+ handler.setBaseResource(Resource.newResource("/path/to/static/resources/"));
+ // Configure directory listing.
+ handler.setDirectoriesListed(false);
+ // Configure welcome files.
+ handler.setWelcomeFiles(new String[]{"index.html"});
+ // Configure whether to accept range requests.
+ handler.setAcceptRanges(true);
+
+ // Link the context to the server.
+ server.setHandler(handler);
+
+ server.start();
+ // end::resourceHandler[]
+ }
+
+ public void multipleResourcesHandler() throws Exception
+ {
+ // tag::multipleResourcesHandler[]
+ ResourceHandler handler = new ResourceHandler();
+
+ // For multiple directories, use ResourceCollection.
+ ResourceCollection directories = new ResourceCollection();
+ directories.addPath("/path/to/static/resources/");
+ directories.addPath("/another/path/to/static/resources/");
+
+ handler.setBaseResource(directories);
+ // end::multipleResourcesHandler[]
+ }
+
+ public void defaultServlet()
+ {
+ // tag::defaultServlet[]
+ // Create a ServletContextHandler with contextPath.
+ ServletContextHandler context = new ServletContextHandler();
+ context.setContextPath("/app");
+
+ // Add the DefaultServlet to serve static content.
+ ServletHolder servletHolder = context.addServlet(DefaultServlet.class, "/");
+ // Configure the DefaultServlet with init-parameters.
+ servletHolder.setInitParameter("resourceBase", "/path/to/static/resources/");
+ servletHolder.setAsyncSupported(true);
+ // end::defaultServlet[]
+ }
+
+ public void serverGzipHandler() throws Exception
+ {
+ // tag::serverGzipHandler[]
+ Server server = new Server();
+ Connector connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ // Create and configure GzipHandler.
+ GzipHandler gzipHandler = new GzipHandler();
+ // Only compress response content larger than this.
+ gzipHandler.setMinGzipSize(1024);
+ // Do not compress these URI paths.
+ gzipHandler.setExcludedPaths("/uncompressed");
+ // Also compress POST responses.
+ gzipHandler.addIncludedMethods("POST");
+ // Do not compress these mime types.
+ gzipHandler.addExcludedMimeTypes("font/ttf");
+
+ // Link a ContextHandlerCollection to manage contexts.
+ ContextHandlerCollection contexts = new ContextHandlerCollection();
+ gzipHandler.setHandler(contexts);
+
+ // Link the GzipHandler to the Server.
+ server.setHandler(gzipHandler);
+
+ server.start();
+ // end::serverGzipHandler[]
+ }
+
+ public void contextGzipHandler() throws Exception
+ {
+ class ShopHandler extends AbstractHandler
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ baseRequest.setHandled(true);
+ // Implement the shop.
+ }
+ }
+
+ class RESTHandler extends AbstractHandler
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ {
+ baseRequest.setHandled(true);
+ // Implement the REST APIs.
+ }
+ }
+
+ Server server = new Server();
+ ServerConnector connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ // tag::contextGzipHandler[]
+ // Create a ContextHandlerCollection to hold contexts.
+ ContextHandlerCollection contextCollection = new ContextHandlerCollection();
+ // Link the ContextHandlerCollection to the Server.
+ server.setHandler(contextCollection);
+
+ // Create the context for the shop web application.
+ ContextHandler shopContext = new ContextHandler("/shop");
+ shopContext.setHandler(new ShopHandler());
+
+ // You want to gzip the shop web application only.
+ GzipHandler shopGzipHandler = new GzipHandler();
+ shopGzipHandler.setHandler(shopContext);
+
+ // Add it to ContextHandlerCollection.
+ contextCollection.addHandler(shopGzipHandler);
+
+ // Create the context for the API web application.
+ ContextHandler apiContext = new ContextHandler("/api");
+ apiContext.setHandler(new RESTHandler());
+
+ // Add it to ContextHandlerCollection.
+ contextCollection.addHandler(apiContext);
+ // end::contextGzipHandler[]
+
+ server.start();
+ }
+
+ public void rewriteHandler() throws Exception
+ {
+ // tag::rewriteHandler[]
+ Server server = new Server();
+ ServerConnector connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ RewriteHandler rewriteHandler = new RewriteHandler();
+ // Compacts URI paths with double slashes, e.g. /ctx//path/to//resource.
+ rewriteHandler.addRule(new CompactPathRule());
+ // Rewrites */products/* to */p/*.
+ rewriteHandler.addRule(new RewriteRegexRule("/(.*)/product/(.*)", "/$1/p/$2"));
+ // Redirects permanently to a different URI.
+ RedirectRegexRule redirectRule = new RedirectRegexRule("/documentation/(.*)", "https://docs.domain.com/$1");
+ redirectRule.setStatusCode(HttpStatus.MOVED_PERMANENTLY_301);
+ rewriteHandler.addRule(redirectRule);
+
+ // Link the RewriteHandler to the Server.
+ server.setHandler(rewriteHandler);
+
+ // Create a ContextHandlerCollection to hold contexts.
+ ContextHandlerCollection contextCollection = new ContextHandlerCollection();
+ // Link the ContextHandlerCollection to the RewriteHandler.
+ rewriteHandler.setHandler(contextCollection);
+
+ server.start();
+ // end::rewriteHandler[]
+ }
+
+ public void statsHandler() throws Exception
+ {
+ // tag::statsHandler[]
+ Server server = new Server();
+ ServerConnector connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ StatisticsHandler statsHandler = new StatisticsHandler();
+
+ // Link the StatisticsHandler to the Server.
+ server.setHandler(statsHandler);
+
+ // Create a ContextHandlerCollection to hold contexts.
+ ContextHandlerCollection contextCollection = new ContextHandlerCollection();
+ // Link the ContextHandlerCollection to the StatisticsHandler.
+ statsHandler.setHandler(contextCollection);
+
+ server.start();
+ // end::statsHandler[]
+ }
+
+ public void securedHandler() throws Exception
+ {
+ // tag::securedHandler[]
+ Server server = new Server();
+
+ // Configure the HttpConfiguration for the clear-text connector.
+ int securePort = 8443;
+ HttpConfiguration httpConfig = new HttpConfiguration();
+ httpConfig.setSecurePort(securePort);
+
+ // The clear-text connector.
+ ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
+ connector.setPort(8080);
+ server.addConnector(connector);
+
+ // Configure the HttpConfiguration for the encrypted connector.
+ HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
+ // Add the SecureRequestCustomizer because we are using TLS.
+ httpConfig.addCustomizer(new SecureRequestCustomizer());
+
+ // The HttpConnectionFactory for the encrypted connector.
+ HttpConnectionFactory http11 = new HttpConnectionFactory(httpsConfig);
+
+ // Configure the SslContextFactory with the keyStore information.
+ SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+ sslContextFactory.setKeyStorePath("/path/to/keystore");
+ sslContextFactory.setKeyStorePassword("secret");
+
+ // The ConnectionFactory for TLS.
+ SslConnectionFactory tls = new SslConnectionFactory(sslContextFactory, http11.getProtocol());
+
+ // The encrypted connector.
+ ServerConnector secureConnector = new ServerConnector(server, tls, http11);
+ secureConnector.setPort(8443);
+ server.addConnector(secureConnector);
+
+ SecuredRedirectHandler securedHandler = new SecuredRedirectHandler();
+
+ // Link the SecuredRedirectHandler to the Server.
+ server.setHandler(securedHandler);
+
+ // Create a ContextHandlerCollection to hold contexts.
+ ContextHandlerCollection contextCollection = new ContextHandlerCollection();
+ // Link the ContextHandlerCollection to the StatisticsHandler.
+ securedHandler.setHandler(contextCollection);
+
+ server.start();
+ // end::securedHandler[]
+ }
+
+ public void defaultHandler() throws Exception
+ {
+ // tag::defaultHandler[]
+ Server server = new Server();
+ Connector connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ // Create a HandlerList.
+ HandlerList handlerList = new HandlerList();
+
+ // Add as first a ContextHandlerCollection to manage contexts.
+ ContextHandlerCollection contexts = new ContextHandlerCollection();
+ handlerList.addHandler(contexts);
+
+ // Add as last a DefaultHandler.
+ DefaultHandler defaultHandler = new DefaultHandler();
+ handlerList.addHandler(defaultHandler);
+
+ // Link the HandlerList to the Server.
+ server.setHandler(handlerList);
+
+ server.start();
+ // end::defaultHandler[]
+ }
+
+ public void continue100()
+ {
+ // tag::continue100[]
+ class Continue100HttpServlet extends HttpServlet
+ {
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ // Inspect the method and headers.
+ boolean isPost = HttpMethod.POST.is(request.getMethod());
+ boolean expects100 = HttpHeaderValue.CONTINUE.is(request.getHeader("Expect"));
+ long contentLength = request.getContentLengthLong();
+
+ if (isPost && expects100)
+ {
+ if (contentLength > 1024 * 1024)
+ {
+ // Rejects uploads that are too large.
+ response.sendError(HttpStatus.PAYLOAD_TOO_LARGE_413);
+ }
+ else
+ {
+ // Getting the request InputStream indicates that
+ // the application wants to read the request content.
+ // Jetty will send the 100 Continue response at this
+ // point, and the client will send the request content.
+ ServletInputStream input = request.getInputStream();
+
+ // Read and process the request input.
+ }
+ }
+ else
+ {
+ // Process normal requests.
+ }
+ }
+ }
+ // end::continue100[]
+ }
+}
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java
new file mode 100644
index 00000000000..8ff63a56e87
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java
@@ -0,0 +1,310 @@
+//
+// ========================================================================
+// 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.docs.programming.server.http2;
+
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MetaData;
+import org.eclipse.jetty.http2.ErrorCode;
+import org.eclipse.jetty.http2.api.Session;
+import org.eclipse.jetty.http2.api.Stream;
+import org.eclipse.jetty.http2.api.server.ServerSessionListener;
+import org.eclipse.jetty.http2.frames.DataFrame;
+import org.eclipse.jetty.http2.frames.HeadersFrame;
+import org.eclipse.jetty.http2.frames.PushPromiseFrame;
+import org.eclipse.jetty.http2.frames.ResetFrame;
+import org.eclipse.jetty.http2.frames.SettingsFrame;
+import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.resource.Resource;
+
+import static java.lang.System.Logger.Level.INFO;
+
+@SuppressWarnings("unused")
+public class HTTP2ServerDocs
+{
+ public void setup() throws Exception
+ {
+ // tag::setup[]
+ // Create a Server instance.
+ Server server = new Server();
+
+ ServerSessionListener sessionListener = new ServerSessionListener.Adapter();
+
+ // Create a ServerConnector with RawHTTP2ServerConnectionFactory.
+ RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(sessionListener);
+
+ // Configure RawHTTP2ServerConnectionFactory, for example:
+
+ // Configure the max number of concurrent requests.
+ http2.setMaxConcurrentStreams(128);
+ // Enable support for CONNECT.
+ http2.setConnectProtocolEnabled(true);
+
+ // Create the ServerConnector.
+ ServerConnector connector = new ServerConnector(server, http2);
+
+ // Add the Connector to the Server
+ server.addConnector(connector);
+
+ // Start the Server so it starts accepting connections from clients.
+ server.start();
+ // end::setup[]
+ }
+
+ public void accept()
+ {
+ // tag::accept[]
+ ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
+ {
+ @Override
+ public void onAccept(Session session)
+ {
+ SocketAddress remoteAddress = session.getRemoteSocketAddress();
+ System.getLogger("http2").log(INFO, "Connection from {0}", remoteAddress);
+ }
+ };
+ // end::accept[]
+ }
+
+ public void preface()
+ {
+ // tag::preface[]
+ ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
+ {
+ @Override
+ public Map onPreface(Session session)
+ {
+ // Customize the settings, for example:
+ Map settings = new HashMap<>();
+
+ // Tell the client that HTTP/2 push is disabled.
+ settings.put(SettingsFrame.ENABLE_PUSH, 0);
+
+ return settings;
+ }
+ };
+ // end::preface[]
+ }
+
+ public void request()
+ {
+ // tag::request[]
+ ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
+ {
+ @Override
+ public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
+ {
+ // This is the "new stream" event, so it's guaranteed to be a request.
+ MetaData.Request request = (MetaData.Request)frame.getMetaData();
+
+ // Return a Stream.Listener to handle the request events,
+ // for example request content events or a request reset.
+ return new Stream.Listener.Adapter();
+ }
+ };
+ // end::request[]
+ }
+
+ public void requestContent()
+ {
+ // tag::requestContent[]
+ ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
+ {
+ @Override
+ public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
+ {
+ MetaData.Request request = (MetaData.Request)frame.getMetaData();
+ // Return a Stream.Listener to handle the request events.
+ return new Stream.Listener.Adapter()
+ {
+ @Override
+ public void onData(Stream stream, DataFrame frame, Callback callback)
+ {
+ // Get the content buffer.
+ ByteBuffer buffer = frame.getData();
+
+ // Consume the buffer, here - as an example - just log it.
+ System.getLogger("http2").log(INFO, "Consuming buffer {0}", buffer);
+
+ // Tell the implementation that the buffer has been consumed.
+ callback.succeeded();
+
+ // By returning from the method, implicitly tell the implementation
+ // to deliver to this method more DATA frames when they are available.
+ }
+ };
+ }
+ };
+ // end::requestContent[]
+ }
+
+ public void response()
+ {
+ // tag::response[]
+ ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
+ {
+ @Override
+ public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
+ {
+ // Send a response after reading the request.
+ MetaData.Request request = (MetaData.Request)frame.getMetaData();
+ if (frame.isEndStream())
+ {
+ respond(stream, request);
+ return null;
+ }
+ else
+ {
+ return new Stream.Listener.Adapter()
+ {
+ @Override
+ public void onData(Stream stream, DataFrame frame, Callback callback)
+ {
+ // Consume the request content.
+ callback.succeeded();
+ if (frame.isEndStream())
+ respond(stream, request);
+ }
+ };
+ }
+ }
+
+ private void respond(Stream stream, MetaData.Request request)
+ {
+ // Prepare the response HEADERS frame.
+
+ // The response HTTP status and HTTP headers.
+ MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
+
+ if (HttpMethod.GET.is(request.getMethod()))
+ {
+ // The response content.
+ ByteBuffer resourceBytes = getResourceBytes(request);
+
+ // Send the HEADERS frame with the response status and headers,
+ // and a DATA frame with the response content bytes.
+ stream.headers(new HeadersFrame(stream.getId(), response, null, false))
+ .thenCompose(s -> s.data(new DataFrame(s.getId(), resourceBytes, true)));
+ }
+ else
+ {
+ // Send just the HEADERS frame with the response status and headers.
+ stream.headers(new HeadersFrame(stream.getId(), response, null, true));
+ }
+ }
+ // tag::exclude[]
+
+ private ByteBuffer getResourceBytes(MetaData.Request request)
+ {
+ return ByteBuffer.allocate(1024);
+ }
+ // end::exclude[]
+ };
+ // end::response[]
+ }
+
+ public void reset()
+ {
+ float maxRequestRate = 0F;
+ // tag::reset[]
+ ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
+ {
+ @Override
+ public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
+ {
+ float requestRate = calculateRequestRate();
+
+ if (requestRate > maxRequestRate)
+ {
+ stream.reset(new ResetFrame(stream.getId(), ErrorCode.REFUSED_STREAM_ERROR.code), Callback.NOOP);
+ return null;
+ }
+ else
+ {
+ // The request is accepted.
+ MetaData.Request request = (MetaData.Request)frame.getMetaData();
+ // Return a Stream.Listener to handle the request events.
+ return new Stream.Listener.Adapter();
+ }
+ }
+ // tag::exclude[]
+
+ private float calculateRequestRate()
+ {
+ return 0F;
+ }
+ // end::exclude[]
+ };
+ // end::reset[]
+ }
+
+ public void push() throws Exception
+ {
+ // tag::push[]
+ // The favicon bytes.
+ ByteBuffer faviconBuffer = BufferUtil.toBuffer(Resource.newResource("/path/to/favicon.ico"), true);
+
+ ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
+ {
+ // By default, push is enabled.
+ private boolean pushEnabled = true;
+
+ @Override
+ public void onSettings(Session session, SettingsFrame frame)
+ {
+ // Check whether the client sent an ENABLE_PUSH setting.
+ Map settings = frame.getSettings();
+ Integer enablePush = settings.get(SettingsFrame.ENABLE_PUSH);
+ if (enablePush != null)
+ pushEnabled = enablePush == 1;
+ }
+
+ @Override
+ public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
+ {
+ MetaData.Request request = (MetaData.Request)frame.getMetaData();
+ if (pushEnabled && request.getURIString().endsWith("/index.html"))
+ {
+ // Push the favicon.
+ HttpURI pushedURI = HttpURI.build(request.getURI()).path("/favicon.ico");
+ MetaData.Request pushedRequest = new MetaData.Request("GET", pushedURI, HttpVersion.HTTP_2, HttpFields.EMPTY);
+ PushPromiseFrame promiseFrame = new PushPromiseFrame(stream.getId(), 0, pushedRequest);
+ stream.push(promiseFrame, new Stream.Listener.Adapter())
+ .thenCompose(pushedStream ->
+ {
+ // Send the favicon "response".
+ MetaData.Response pushedResponse = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
+ return pushedStream.headers(new HeadersFrame(pushedStream.getId(), pushedResponse, null, false))
+ .thenCompose(pushed -> pushed.data(new DataFrame(pushed.getId(), faviconBuffer, true)));
+ });
+ }
+ // Return a Stream.Listener to handle the request events.
+ return new Stream.Listener.Adapter();
+ }
+ };
+ // end::push[]
+ }
+}
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/http3/HTTP3ServerDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/http3/HTTP3ServerDocs.java
new file mode 100644
index 00000000000..e113bbd686d
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/http3/HTTP3ServerDocs.java
@@ -0,0 +1,334 @@
+//
+// ========================================================================
+// 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.docs.programming.server.http3;
+
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.RejectedExecutionException;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MetaData;
+import org.eclipse.jetty.http3.api.Session;
+import org.eclipse.jetty.http3.api.Stream;
+import org.eclipse.jetty.http3.frames.DataFrame;
+import org.eclipse.jetty.http3.frames.HeadersFrame;
+import org.eclipse.jetty.http3.internal.HTTP3ErrorCode;
+import org.eclipse.jetty.http3.server.HTTP3ServerConnector;
+import org.eclipse.jetty.http3.server.RawHTTP3ServerConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+import static java.lang.System.Logger.Level.INFO;
+
+@SuppressWarnings("unused")
+public class HTTP3ServerDocs
+{
+ public void setup() throws Exception
+ {
+ // tag::setup[]
+ // Create a Server instance.
+ Server server = new Server();
+
+ // HTTP/3 is always secure, so it always need a SslContextFactory.
+ SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+ sslContextFactory.setKeyStorePath("/path/to/keystore");
+ sslContextFactory.setKeyStorePassword("secret");
+
+ // The listener for session events.
+ Session.Server.Listener sessionListener = new Session.Server.Listener() {};
+
+ // Create and configure the RawHTTP3ServerConnectionFactory.
+ RawHTTP3ServerConnectionFactory http3 = new RawHTTP3ServerConnectionFactory(sessionListener);
+ http3.getHTTP3Configuration().setStreamIdleTimeout(15000);
+
+ // Create and configure the HTTP3ServerConnector.
+ HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactory, http3);
+ // Configure the max number of requests per QUIC connection.
+ connector.getQuicConfiguration().setMaxBidirectionalRemoteStreams(1024);
+
+ // Add the Connector to the Server.
+ server.addConnector(connector);
+
+ // Start the Server so it starts accepting connections from clients.
+ server.start();
+ // end::setup[]
+ }
+
+ public void accept()
+ {
+ // tag::accept[]
+ Session.Server.Listener sessionListener = new Session.Server.Listener()
+ {
+ @Override
+ public void onAccept(Session session)
+ {
+ SocketAddress remoteAddress = session.getRemoteSocketAddress();
+ System.getLogger("http3").log(INFO, "Connection from {0}", remoteAddress);
+ }
+ };
+ // end::accept[]
+ }
+
+ public void preface()
+ {
+ // tag::preface[]
+ Session.Server.Listener sessionListener = new Session.Server.Listener()
+ {
+ @Override
+ public Map onPreface(Session session)
+ {
+ Map settings = new HashMap<>();
+
+ // Customize the settings
+
+ return settings;
+ }
+ };
+ // end::preface[]
+ }
+
+ public void request()
+ {
+ // tag::request[]
+ Session.Server.Listener sessionListener = new Session.Server.Listener()
+ {
+ @Override
+ public Stream.Server.Listener onRequest(Stream.Server stream, HeadersFrame frame)
+ {
+ MetaData.Request request = (MetaData.Request)frame.getMetaData();
+
+ // Return a Stream.Server.Listener to handle the request events,
+ // for example request content events or a request reset.
+ return new Stream.Server.Listener() {};
+ }
+ };
+ // end::request[]
+ }
+
+ public void requestContent()
+ {
+ // tag::requestContent[]
+ Session.Server.Listener sessionListener = new Session.Server.Listener()
+ {
+ @Override
+ public Stream.Server.Listener onRequest(Stream.Server stream, HeadersFrame frame)
+ {
+ MetaData.Request request = (MetaData.Request)frame.getMetaData();
+
+ // Demand to be called back when data is available.
+ stream.demand();
+
+ // Return a Stream.Server.Listener to handle the request content.
+ return new Stream.Server.Listener()
+ {
+ @Override
+ public void onDataAvailable(Stream.Server stream)
+ {
+ // Read a chunk of the request content.
+ Stream.Data data = stream.readData();
+
+ if (data == null)
+ {
+ // No data available now, demand to be called back.
+ stream.demand();
+ }
+ else
+ {
+ // Get the content buffer.
+ ByteBuffer buffer = data.getByteBuffer();
+
+ // Consume the buffer, here - as an example - just log it.
+ System.getLogger("http3").log(INFO, "Consuming buffer {0}", buffer);
+
+ // Tell the implementation that the buffer has been consumed.
+ data.complete();
+
+ if (!data.isLast())
+ {
+ // Demand to be called back.
+ stream.demand();
+ }
+ }
+ }
+ };
+ }
+ };
+ // end::requestContent[]
+ }
+
+ public void response()
+ {
+ // tag::response[]
+ Session.Server.Listener sessionListener = new Session.Server.Listener()
+ {
+ @Override
+ public Stream.Server.Listener onRequest(Stream.Server stream, HeadersFrame frame)
+ {
+ // Send a response after reading the request.
+ MetaData.Request request = (MetaData.Request)frame.getMetaData();
+ if (frame.isLast())
+ {
+ respond(stream, request);
+ return null;
+ }
+ else
+ {
+ // Demand to be called back when data is available.
+ stream.demand();
+ return new Stream.Server.Listener()
+ {
+ @Override
+ public void onDataAvailable(Stream.Server stream)
+ {
+ Stream.Data data = stream.readData();
+ if (data == null)
+ {
+ stream.demand();
+ }
+ else
+ {
+ // Consume the request content.
+ data.complete();
+ if (data.isLast())
+ respond(stream, request);
+ }
+ }
+ };
+ }
+ }
+
+ private void respond(Stream.Server stream, MetaData.Request request)
+ {
+ // Prepare the response HEADERS frame.
+
+ // The response HTTP status and HTTP headers.
+ MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_3, HttpStatus.OK_200, HttpFields.EMPTY);
+
+ if (HttpMethod.GET.is(request.getMethod()))
+ {
+ // The response content.
+ ByteBuffer resourceBytes = getResourceBytes(request);
+
+ // Send the HEADERS frame with the response status and headers,
+ // and a DATA frame with the response content bytes.
+ stream.respond(new HeadersFrame(response, false))
+ .thenCompose(s -> s.data(new DataFrame(resourceBytes, true)));
+ }
+ else
+ {
+ // Send just the HEADERS frame with the response status and headers.
+ stream.respond(new HeadersFrame(response, true));
+ }
+ }
+ // tag::exclude[]
+
+ private ByteBuffer getResourceBytes(MetaData.Request request)
+ {
+ return ByteBuffer.allocate(1024);
+ }
+ // end::exclude[]
+ };
+ // end::response[]
+ }
+
+ public void reset()
+ {
+ float maxRequestRate = 0F;
+ // tag::reset[]
+ Session.Server.Listener sessionListener = new Session.Server.Listener()
+ {
+ @Override
+ public Stream.Server.Listener onRequest(Stream.Server stream, HeadersFrame frame)
+ {
+ float requestRate = calculateRequestRate();
+
+ if (requestRate > maxRequestRate)
+ {
+ stream.reset(HTTP3ErrorCode.REQUEST_REJECTED_ERROR.code(), new RejectedExecutionException());
+ return null;
+ }
+ else
+ {
+ // The request is accepted.
+ MetaData.Request request = (MetaData.Request)frame.getMetaData();
+ // Return a Stream.Listener to handle the request events.
+ return new Stream.Server.Listener() {};
+ }
+ }
+ // tag::exclude[]
+
+ private float calculateRequestRate()
+ {
+ return 0F;
+ }
+ // end::exclude[]
+ };
+ // end::reset[]
+ }
+
+ // TODO: push not yet implemented in HTTP/3.
+/*
+ public void push() throws Exception
+ {
+ // tag::push[]
+ // The favicon bytes.
+ ByteBuffer faviconBuffer = BufferUtil.toBuffer(Resource.newResource("/path/to/favicon.ico"), true);
+
+ ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
+ {
+ // By default, push is enabled.
+ private boolean pushEnabled = true;
+
+ @Override
+ public void onSettings(Session session, SettingsFrame frame)
+ {
+ // Check whether the client sent an ENABLE_PUSH setting.
+ Map settings = frame.getSettings();
+ Integer enablePush = settings.get(SettingsFrame.ENABLE_PUSH);
+ if (enablePush != null)
+ pushEnabled = enablePush == 1;
+ }
+
+ @Override
+ public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
+ {
+ MetaData.Request request = (MetaData.Request)frame.getMetaData();
+ if (pushEnabled && request.getURIString().endsWith("/index.html"))
+ {
+ // Push the favicon.
+ HttpURI pushedURI = HttpURI.build(request.getURI()).path("/favicon.ico");
+ MetaData.Request pushedRequest = new MetaData.Request("GET", pushedURI, HttpVersion.HTTP_2, HttpFields.EMPTY);
+ PushPromiseFrame promiseFrame = new PushPromiseFrame(stream.getId(), 0, pushedRequest);
+ stream.push(promiseFrame, new Stream.Listener.Adapter())
+ .thenCompose(pushedStream ->
+ {
+ // Send the favicon "response".
+ MetaData.Response pushedResponse = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
+ return pushedStream.headers(new HeadersFrame(pushedStream.getId(), pushedResponse, null, false))
+ .thenCompose(pushed -> pushed.data(new DataFrame(pushed.getId(), faviconBuffer, true)));
+ });
+ }
+ // Return a Stream.Listener to handle the request events.
+ return new Stream.Listener.Adapter();
+ }
+ };
+ // end::push[]
+ }
+ */
+}
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java
new file mode 100644
index 00000000000..8a4b42f4dd5
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java
@@ -0,0 +1,307 @@
+//
+// ========================================================================
+// 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.docs.programming.server.session;
+
+import java.io.File;
+import java.net.InetSocketAddress;
+
+import org.eclipse.jetty.memcached.session.MemcachedSessionDataMapFactory;
+import org.eclipse.jetty.nosql.mongodb.MongoSessionDataStoreFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.session.CachingSessionDataStoreFactory;
+import org.eclipse.jetty.server.session.DatabaseAdaptor;
+import org.eclipse.jetty.server.session.DefaultSessionCache;
+import org.eclipse.jetty.server.session.DefaultSessionCacheFactory;
+import org.eclipse.jetty.server.session.DefaultSessionIdManager;
+import org.eclipse.jetty.server.session.FileSessionDataStore;
+import org.eclipse.jetty.server.session.FileSessionDataStoreFactory;
+import org.eclipse.jetty.server.session.HouseKeeper;
+import org.eclipse.jetty.server.session.NullSessionCache;
+import org.eclipse.jetty.server.session.NullSessionCacheFactory;
+import org.eclipse.jetty.server.session.NullSessionDataStore;
+import org.eclipse.jetty.server.session.SessionCache;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+@SuppressWarnings("unused")
+public class SessionDocs
+{
+ public void minimumDefaultSessionIdManager()
+ {
+ //tag::default[]
+ Server server = new Server();
+ DefaultSessionIdManager idMgr = new DefaultSessionIdManager(server);
+ //you must set the workerName unless you set the env viable JETTY_WORKER_NAME
+ idMgr.setWorkerName("server3");
+ server.setSessionIdManager(idMgr);
+ //end::default[]
+ }
+
+ public void defaultSessionIdManagerWithHouseKeeper()
+ {
+ try
+ {
+ //tag::housekeeper[]
+ Server server = new Server();
+ DefaultSessionIdManager idMgr = new DefaultSessionIdManager(server);
+ idMgr.setWorkerName("server7");
+ server.setSessionIdManager(idMgr);
+
+ HouseKeeper houseKeeper = new HouseKeeper();
+ houseKeeper.setSessionIdManager(idMgr);
+ //set the frequency of scavenge cycles
+ houseKeeper.setIntervalSec(600L);
+ idMgr.setSessionHouseKeeper(houseKeeper);
+ //end::housekeeper[]
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void servletContextWithSessionHandler()
+ {
+ //tag:schsession[]
+ Server server = new Server();
+
+ ServletContextHandler context = new ServletContextHandler(server, "/foo", ServletContextHandler.SESSIONS);
+ SessionHandler sessions = context.getSessionHandler();
+ //make idle sessions valid for only 5mins
+ sessions.setMaxInactiveInterval(300);
+ //turn off use of cookies
+ sessions.setUsingCookies(false);
+
+ server.setHandler(context);
+ //end::schsession[]
+ }
+
+ public void webAppWithSessionHandler()
+ {
+ //tag:wacsession[]
+ Server server = new Server();
+
+ WebAppContext context = new WebAppContext();
+ SessionHandler sessions = context.getSessionHandler();
+ //make idle sessions valid for only 5mins
+ sessions.setMaxInactiveInterval(300);
+ //turn off use of cookies
+ sessions.setUsingCookies(false);
+
+ server.setHandler(context);
+ //end::wacsession[]
+ }
+
+ public void defaultSessionCache()
+ {
+ //tag::defaultsessioncache[]
+ Server server = new Server();
+
+ DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
+ //EVICT_ON_INACTIVE: evict a session after 60sec inactivity
+ cacheFactory.setEvictionPolicy(60);
+ //Only useful with the EVICT_ON_INACTIVE policy
+ cacheFactory.setSaveOnInactiveEvict(true);
+ cacheFactory.setFlushOnResponseCommit(true);
+ cacheFactory.setInvalidateOnShutdown(false);
+ cacheFactory.setRemoveUnloadableSessions(true);
+ cacheFactory.setSaveOnCreate(true);
+
+ //Add the factory as a bean to the server, now whenever a
+ //SessionHandler starts it will consult the bean to create a new DefaultSessionCache
+ server.addBean(cacheFactory);
+ //end::defaultsessioncache[]
+ }
+
+ public void nullSessionCache()
+ {
+ //tag::nullsessioncache[]
+ Server server = new Server();
+ NullSessionCacheFactory cacheFactory = new NullSessionCacheFactory();
+ cacheFactory.setFlushOnResponseCommit(true);
+ cacheFactory.setRemoveUnloadableSessions(true);
+ cacheFactory.setSaveOnCreate(true);
+
+ //Add the factory as a bean to the server, now whenever a
+ //SessionHandler starts it will consult the bean to create a new NullSessionCache
+ server.addBean(cacheFactory);
+ //end::nullsessioncache[]
+ }
+
+ public void mixedSessionCache()
+ {
+ //tag::mixedsessioncache[]
+ Server server = new Server();
+
+ DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
+ //NEVER_EVICT
+ cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT);
+ cacheFactory.setFlushOnResponseCommit(true);
+ cacheFactory.setInvalidateOnShutdown(false);
+ cacheFactory.setRemoveUnloadableSessions(true);
+ cacheFactory.setSaveOnCreate(true);
+
+ //Add the factory as a bean to the server, now whenever a
+ //SessionHandler starts it will consult the bean to create a new DefaultSessionCache
+ server.addBean(cacheFactory);
+
+ ContextHandlerCollection contexts = new ContextHandlerCollection();
+ server.setHandler(contexts);
+
+ //Add a webapp that will use a DefaultSessionCache via the DefaultSessionCacheFactory
+ WebAppContext app1 = new WebAppContext();
+ app1.setContextPath("/app1");
+ contexts.addHandler(app1);
+
+ //Add a webapp that uses an explicit NullSessionCache instead
+ WebAppContext app2 = new WebAppContext();
+ app2.setContextPath("/app2");
+ NullSessionCache nullSessionCache = new NullSessionCache(app2.getSessionHandler());
+ nullSessionCache.setFlushOnResponseCommit(true);
+ nullSessionCache.setRemoveUnloadableSessions(true);
+ nullSessionCache.setSaveOnCreate(true);
+ //If we pass an existing SessionCache instance to the SessionHandler, it must be
+ //fully configured: this means we must also provide SessionDataStore
+ nullSessionCache.setSessionDataStore(new NullSessionDataStore());
+ app2.getSessionHandler().setSessionCache(nullSessionCache);
+ //end::mixedsessioncache[]
+ }
+
+ public void fileSessionDataStoreFactory()
+ {
+ //tag::filesessiondatastorefactory[]
+ Server server = new Server();
+
+ //First lets configure a DefaultSessionCacheFactory
+ DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
+ //NEVER_EVICT
+ cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT);
+ cacheFactory.setFlushOnResponseCommit(true);
+ cacheFactory.setInvalidateOnShutdown(false);
+ cacheFactory.setRemoveUnloadableSessions(true);
+ cacheFactory.setSaveOnCreate(true);
+
+ //Add the factory as a bean to the server, now whenever a
+ //SessionHandler starts it will consult the bean to create a new DefaultSessionCache
+ server.addBean(cacheFactory);
+
+ //Now, lets configure a FileSessionDataStoreFactory
+ FileSessionDataStoreFactory storeFactory = new FileSessionDataStoreFactory();
+ storeFactory.setStoreDir(new File("/tmp/sessions"));
+ storeFactory.setGracePeriodSec(3600);
+ storeFactory.setSavePeriodSec(0);
+
+ //Add the factory as a bean on the server, now whenever a
+ //SessionHandler starts, it will consult the bean to create a new FileSessionDataStore
+ //for use by the DefaultSessionCache
+ server.addBean(storeFactory);
+ //end::filesessiondatastorefactory[]
+ }
+
+ public void fileSessionDataStore()
+ {
+ //tag::filesessiondatastore[]
+
+ //create a context
+ WebAppContext app1 = new WebAppContext();
+ app1.setContextPath("/app1");
+
+ //First, we create a DefaultSessionCache
+ DefaultSessionCache cache = new DefaultSessionCache(app1.getSessionHandler());
+ cache.setEvictionPolicy(SessionCache.NEVER_EVICT);
+ cache.setFlushOnResponseCommit(true);
+ cache.setInvalidateOnShutdown(false);
+ cache.setRemoveUnloadableSessions(true);
+ cache.setSaveOnCreate(true);
+
+ //Now, we configure a FileSessionDataStore
+ FileSessionDataStore store = new FileSessionDataStore();
+ store.setStoreDir(new File("/tmp/sessions"));
+ store.setGracePeriodSec(3600);
+ store.setSavePeriodSec(0);
+
+ //Tell the cache to use the store
+ cache.setSessionDataStore(store);
+
+ //Tell the contex to use the cache/store combination
+ app1.getSessionHandler().setSessionCache(cache);
+
+ //end::filesessiondatastore[]
+ }
+
+ public void cachingSessionDataStore()
+ {
+ //tag::cachingsds[]
+ Server server = new Server();
+
+ //Make a factory for memcached L2 caches for SessionData
+ MemcachedSessionDataMapFactory mapFactory = new MemcachedSessionDataMapFactory();
+ mapFactory.setExpirySec(0); //items in memcached don't expire
+ mapFactory.setHeartbeats(true); //tell memcached to use heartbeats
+ mapFactory.setAddresses(new InetSocketAddress("localhost", 11211)); //use a local memcached instance
+ mapFactory.setWeights(new int[] {100}); //set the weighting
+
+
+ //Make a FileSessionDataStoreFactory for creating FileSessionDataStores
+ //to persist the session data
+ FileSessionDataStoreFactory storeFactory = new FileSessionDataStoreFactory();
+ storeFactory.setStoreDir(new File("/tmp/sessions"));
+ storeFactory.setGracePeriodSec(3600);
+ storeFactory.setSavePeriodSec(0);
+
+ //Make a factory that plugs the L2 cache into the SessionDataStore
+ CachingSessionDataStoreFactory cachingSessionDataStoreFactory = new CachingSessionDataStoreFactory();
+ cachingSessionDataStoreFactory.setSessionDataMapFactory(mapFactory);
+ cachingSessionDataStoreFactory.setSessionStoreFactory(storeFactory);
+
+ //Register it as a bean so that all SessionHandlers will use it
+ //to make FileSessionDataStores that use memcached as an L2 SessionData cache.
+ server.addBean(cachingSessionDataStoreFactory);
+ //end::cachingsds[]
+ }
+
+ public void jdbcSessionDataStore()
+ {
+ //tag::dbaDatasource[]
+ DatabaseAdaptor datasourceAdaptor = new DatabaseAdaptor();
+ datasourceAdaptor.setDatasourceName("/jdbc/myDS");
+ //end::dbaDatasource[]
+
+ //tag::dbaDriver[]
+ DatabaseAdaptor driverAdaptor = new DatabaseAdaptor();
+ driverAdaptor.setDriverInfo("com.mysql.jdbc.Driver", "jdbc:mysql://127.0.0.1:3306/sessions?user=sessionsadmin");
+ //end::dbaDriver[]
+ }
+
+ public void mongoSessionDataStore()
+ {
+ //tag::mongosdfactory[]
+ Server server = new Server();
+
+ MongoSessionDataStoreFactory mongoSessionDataStoreFactory = new MongoSessionDataStoreFactory();
+ mongoSessionDataStoreFactory.setGracePeriodSec(3600);
+ mongoSessionDataStoreFactory.setSavePeriodSec(0);
+ mongoSessionDataStoreFactory.setDbName("HttpSessions");
+ mongoSessionDataStoreFactory.setCollectionName("JettySessions");
+
+ // Either set the connectionString
+ mongoSessionDataStoreFactory.setConnectionString("mongodb:://localhost:27017");
+ // or alternatively set the host and port.
+ mongoSessionDataStoreFactory.setHost("localhost");
+ mongoSessionDataStoreFactory.setPort(27017);
+ //end::mongosdfactory[]
+ }
+}
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java
new file mode 100644
index 00000000000..50eee426146
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java
@@ -0,0 +1,388 @@
+//
+// ========================================================================
+// 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.docs.programming.server.websocket;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.http.pathmap.PathSpec;
+import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
+import org.eclipse.jetty.websocket.server.JettyWebSocketCreator;
+import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer;
+import org.eclipse.jetty.websocket.server.JettyWebSocketServlet;
+import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory;
+import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
+
+@SuppressWarnings("unused")
+public class WebSocketServerDocs
+{
+ public void standardContainerWebAppContext() throws Exception
+ {
+ // tag::standardContainerWebAppContext[]
+ // Create a Server with a ServerConnector listening on port 8080.
+ Server server = new Server(8080);
+
+ // Create a WebAppContext with the given context path.
+ WebAppContext handler = new WebAppContext("/path/to/webapp", "/ctx");
+ server.setHandler(handler);
+
+ // Starting the Server will start the WebAppContext.
+ server.start();
+ // end::standardContainerWebAppContext[]
+ }
+
+ public void standardContainerServletContextHandler() throws Exception
+ {
+ // tag::standardContainerServletContextHandler[]
+ // Create a Server with a ServerConnector listening on port 8080.
+ Server server = new Server(8080);
+
+ // Create a ServletContextHandler with the given context path.
+ ServletContextHandler handler = new ServletContextHandler(server, "/ctx");
+ server.setHandler(handler);
+
+ // Ensure that JavaxWebSocketServletContainerInitializer is initialized,
+ // to setup the ServerContainer for this web application context.
+ JavaxWebSocketServletContainerInitializer.configure(handler, null);
+
+ // Starting the Server will start the ServletContextHandler.
+ server.start();
+ // end::standardContainerServletContextHandler[]
+ }
+
+ public void standardEndpointsInitialization() throws Exception
+ {
+ // tag::standardEndpointsInitialization[]
+ // Create a Server with a ServerConnector listening on port 8080.
+ Server server = new Server(8080);
+
+ // Create a ServletContextHandler with the given context path.
+ ServletContextHandler handler = new ServletContextHandler(server, "/ctx");
+ server.setHandler(handler);
+
+ // Ensure that JavaxWebSocketServletContainerInitializer is initialized,
+ // to setup the ServerContainer for this web application context.
+ JavaxWebSocketServletContainerInitializer.configure(handler, null);
+
+ // Add a WebSocket-initializer Servlet to register WebSocket endpoints.
+ handler.addServlet(MyJavaxWebSocketInitializerServlet.class, "/*");
+
+ // Starting the Server will start the ServletContextHandler.
+ server.start();
+ // end::standardEndpointsInitialization[]
+ }
+
+ @SuppressWarnings("InnerClassMayBeStatic")
+ // tag::standardWebSocketInitializerServlet[]
+ public class MyJavaxWebSocketInitializerServlet extends HttpServlet
+ {
+ @Override
+ public void init() throws ServletException
+ {
+ try
+ {
+ // Retrieve the ServerContainer from the ServletContext attributes.
+ ServerContainer container = (ServerContainer)getServletContext().getAttribute(ServerContainer.class.getName());
+
+ // Configure the ServerContainer.
+ container.setDefaultMaxTextMessageBufferSize(128 * 1024);
+
+ // Simple registration of your WebSocket endpoints.
+ container.addEndpoint(MyJavaxWebSocketEndPoint.class);
+
+ // Advanced registration of your WebSocket endpoints.
+ container.addEndpoint(
+ ServerEndpointConfig.Builder.create(MyJavaxWebSocketEndPoint.class, "/ws")
+ .subprotocols(List.of("my-ws-protocol"))
+ .build()
+ );
+ }
+ catch (DeploymentException x)
+ {
+ throw new ServletException(x);
+ }
+ }
+ }
+ // end::standardWebSocketInitializerServlet[]
+
+ public void standardContainerAndEndpoints() throws Exception
+ {
+ // tag::standardContainerAndEndpoints[]
+ // Create a Server with a ServerConnector listening on port 8080.
+ Server server = new Server(8080);
+
+ // Create a ServletContextHandler with the given context path.
+ ServletContextHandler handler = new ServletContextHandler(server, "/ctx");
+ server.setHandler(handler);
+
+ // Setup the ServerContainer and the WebSocket endpoints for this web application context.
+ JavaxWebSocketServletContainerInitializer.configure(handler, (servletContext, container) ->
+ {
+ // Configure the ServerContainer.
+ container.setDefaultMaxTextMessageBufferSize(128 * 1024);
+
+ // Simple registration of your WebSocket endpoints.
+ container.addEndpoint(MyJavaxWebSocketEndPoint.class);
+
+ // Advanced registration of your WebSocket endpoints.
+ container.addEndpoint(
+ ServerEndpointConfig.Builder.create(MyJavaxWebSocketEndPoint.class, "/ws")
+ .subprotocols(List.of("my-ws-protocol"))
+ .build()
+ );
+ });
+
+ // Starting the Server will start the ServletContextHandler.
+ server.start();
+ // end::standardContainerAndEndpoints[]
+ }
+
+ public void jettyContainerServletContextHandler() throws Exception
+ {
+ // tag::jettyContainerServletContextHandler[]
+ // Create a Server with a ServerConnector listening on port 8080.
+ Server server = new Server(8080);
+
+ // Create a ServletContextHandler with the given context path.
+ ServletContextHandler handler = new ServletContextHandler(server, "/ctx");
+ server.setHandler(handler);
+
+ // Ensure that JettyWebSocketServletContainerInitializer is initialized,
+ // to setup the JettyWebSocketServerContainer for this web application context.
+ JettyWebSocketServletContainerInitializer.configure(handler, null);
+
+ // Starting the Server will start the ServletContextHandler.
+ server.start();
+ // end::jettyContainerServletContextHandler[]
+ }
+
+ public void jettyEndpointsInitialization() throws Exception
+ {
+ // tag::jettyEndpointsInitialization[]
+ // Create a Server with a ServerConnector listening on port 8080.
+ Server server = new Server(8080);
+
+ // Create a ServletContextHandler with the given context path.
+ ServletContextHandler handler = new ServletContextHandler(server, "/ctx");
+ server.setHandler(handler);
+
+ // Ensure that JettyWebSocketServletContainerInitializer is initialized,
+ // to setup the JettyWebSocketServerContainer for this web application context.
+ JettyWebSocketServletContainerInitializer.configure(handler, null);
+
+ // Add a WebSocket-initializer Servlet to register WebSocket endpoints.
+ handler.addServlet(MyJettyWebSocketInitializerServlet.class, "/*");
+
+ // Starting the Server will start the ServletContextHandler.
+ server.start();
+ // end::jettyEndpointsInitialization[]
+ }
+
+ @SuppressWarnings("InnerClassMayBeStatic")
+ // tag::jettyWebSocketInitializerServlet[]
+ public class MyJettyWebSocketInitializerServlet extends HttpServlet
+ {
+ @Override
+ public void init() throws ServletException
+ {
+ // Retrieve the JettyWebSocketServerContainer.
+ JettyWebSocketServerContainer container = JettyWebSocketServerContainer.getContainer(getServletContext());
+
+ // Configure the JettyWebSocketServerContainer.
+ container.setMaxTextMessageSize(128 * 1024);
+
+ // Simple registration of your WebSocket endpoints.
+ container.addMapping("/ws/myURI", MyJettyWebSocketEndPoint.class);
+
+ // Advanced registration of your WebSocket endpoints.
+ container.addMapping("/ws/myOtherURI", (upgradeRequest, upgradeResponse) ->
+ new MyOtherJettyWebSocketEndPoint()
+ );
+ }
+ }
+ // end::jettyWebSocketInitializerServlet[]
+
+ public void jettyContainerAndEndpoints() throws Exception
+ {
+ // tag::jettyContainerAndEndpoints[]
+ // Create a Server with a ServerConnector listening on port 8080.
+ Server server = new Server(8080);
+
+ // Create a ServletContextHandler with the given context path.
+ ServletContextHandler handler = new ServletContextHandler(server, "/ctx");
+ server.setHandler(handler);
+
+ // Setup the JettyWebSocketServerContainer and the WebSocket endpoints for this web application context.
+ JettyWebSocketServletContainerInitializer.configure(handler, (servletContext, container) ->
+ {
+ // Configure the ServerContainer.
+ container.setMaxTextMessageSize(128 * 1024);
+
+ // Add your WebSocket endpoint(s) to the JettyWebSocketServerContainer.
+ container.addMapping("/ws/myURI", MyJettyWebSocketEndPoint.class);
+
+ // Use JettyWebSocketCreator to have more control on the WebSocket endpoint creation.
+ container.addMapping("/ws/myOtherURI", (upgradeRequest, upgradeResponse) ->
+ {
+ // Possibly inspect the upgrade request and modify the upgrade response.
+ upgradeResponse.setAcceptedSubProtocol("my-ws-protocol");
+
+ // Create the new WebSocket endpoint.
+ return new MyOtherJettyWebSocketEndPoint();
+ });
+ });
+
+ // Starting the Server will start the ServletContextHandler.
+ server.start();
+ // end::jettyContainerAndEndpoints[]
+ }
+
+ @SuppressWarnings("InnerClassMayBeStatic")
+ // tag::jettyContainerUpgrade[]
+ public class ProgrammaticWebSocketUpgradeServlet extends HttpServlet
+ {
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ if (requiresWebSocketUpgrade(request))
+ {
+ // Retrieve the JettyWebSocketServerContainer.
+ JettyWebSocketServerContainer container = JettyWebSocketServerContainer.getContainer(getServletContext());
+
+ // Use a JettyWebSocketCreator to inspect the upgrade request,
+ // possibly modify the upgrade response, and create the WebSocket endpoint.
+ JettyWebSocketCreator creator = (upgradeRequest, upgradeResponse) -> new MyJettyWebSocketEndPoint();
+
+ // Perform the direct WebSocket upgrade.
+ container.upgrade(creator, request, response);
+ }
+ else
+ {
+ // Normal handling of the HTTP request/response.
+ }
+ }
+ }
+ // end::jettyContainerUpgrade[]
+
+ private boolean requiresWebSocketUpgrade(HttpServletRequest request)
+ {
+ return false;
+ }
+
+ public void jettyWebSocketServletMain() throws Exception
+ {
+ // tag::jettyWebSocketServletMain[]
+ // Create a Server with a ServerConnector listening on port 8080.
+ Server server = new Server(8080);
+
+ // Create a ServletContextHandler with the given context path.
+ ServletContextHandler handler = new ServletContextHandler(server, "/ctx");
+ server.setHandler(handler);
+
+ // Setup the JettyWebSocketServerContainer to initialize WebSocket components.
+ JettyWebSocketServletContainerInitializer.configure(handler, null);
+
+ // Add your WebSocketServlet subclass to the ServletContextHandler.
+ handler.addServlet(MyJettyWebSocketServlet.class, "/ws/*");
+
+ // Starting the Server will start the ServletContextHandler.
+ server.start();
+ // end::jettyWebSocketServletMain[]
+ }
+
+ @SuppressWarnings("InnerClassMayBeStatic")
+ // tag::jettyWebSocketServlet[]
+ public class MyJettyWebSocketServlet extends JettyWebSocketServlet
+ {
+ @Override
+ protected void configure(JettyWebSocketServletFactory factory)
+ {
+ // At most 1 MiB text messages.
+ factory.setMaxTextMessageSize(1048576);
+
+ // Add the WebSocket endpoint.
+ factory.addMapping("/ws/someURI", (upgradeRequest, upgradeResponse) ->
+ {
+ // Possibly inspect the upgrade request and modify the upgrade response.
+
+ // Create the new WebSocket endpoint.
+ return new MyJettyWebSocketEndPoint();
+ });
+ }
+ }
+ // end::jettyWebSocketServlet[]
+
+ @ServerEndpoint("/ws")
+ private static class MyJavaxWebSocketEndPoint
+ {
+ }
+
+ @WebSocket
+ private static class MyJettyWebSocketEndPoint
+ {
+ }
+
+ @WebSocket
+ private static class MyOtherJettyWebSocketEndPoint
+ {
+ }
+
+ public void uriTemplatePathSpec()
+ {
+ Server server = new Server(8080);
+
+ // tag::uriTemplatePathSpec[]
+ ServletContextHandler handler = new ServletContextHandler(server, "/ctx");
+
+ // Configure the JettyWebSocketServerContainer.
+ JettyWebSocketServletContainerInitializer.configure(handler, (servletContext, container) ->
+ {
+ container.addMapping("/ws/chat/{room}", (upgradeRequest, upgradeResponse) ->
+ {
+ // Retrieve the URI template.
+ UriTemplatePathSpec pathSpec = (UriTemplatePathSpec)upgradeRequest.getServletAttribute(PathSpec.class.getName());
+
+ // Match the URI template.
+ Map params = pathSpec.getPathParams(upgradeRequest.getRequestPath());
+ String room = params.get("room");
+
+ // Create the new WebSocket endpoint with the URI template information.
+ return new MyWebSocketRoomEndPoint(room);
+ });
+ });
+ // end::uriTemplatePathSpec[]
+ }
+
+ @WebSocket
+ private static class MyWebSocketRoomEndPoint
+ {
+ public MyWebSocketRoomEndPoint(String room)
+ {
+ }
+ }
+}
diff --git a/documentation/jetty/modules/operations-guide/images/jmc-server-dump.png b/documentation/jetty/modules/operations-guide/images/jmc-server-dump.png
new file mode 100644
index 00000000000..33cd92938cb
Binary files /dev/null and b/documentation/jetty/modules/operations-guide/images/jmc-server-dump.png differ
diff --git a/documentation/jetty/modules/operations-guide/nav.adoc b/documentation/jetty/modules/operations-guide/nav.adoc
new file mode 100644
index 00000000000..fe0d38050dc
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/nav.adoc
@@ -0,0 +1,39 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+.xref:operations-guide:index.adoc[]
+* xref:begin/index.adoc[]
+* xref:features/index.adoc[]
+* xref:howtos/index.adoc[]
+* xref:arch/index.adoc[]
+* xref:start/index.adoc[]
+** xref:start/start-jpms.adoc[]
+* xref:modules/index.adoc[]
+** xref:modules/custom.adoc[]
+** xref:modules/standard.adoc[]
+* xref:deploy/index.adoc[]
+* xref:server/index.adoc[]
+* xref:protocols/index.adoc[]
+* xref:keystore/index.adoc[]
+* xref:session/index.adoc[]
+* xref:quickstart/index.adoc[]
+* xref:annotations/index.adoc[]
+* xref:jsp/index.adoc[]
+* xref:jstl/index.adoc[]
+* xref:jsf-taglibs/index.adoc[]
+* xref:jndi/index.adoc[]
+* xref:jaas/index.adoc[]
+* xref:jaspi/index.adoc[]
+* xref:jmx/index.adoc[]
+* xref:troubleshooting/index.adoc[]
+* xref:xml/index.adoc[]
diff --git a/documentation/jetty/modules/operations-guide/pages/annotations/index.adoc b/documentation/jetty/modules/operations-guide/pages/annotations/index.adoc
new file mode 100644
index 00000000000..240a86c5639
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/annotations/index.adoc
@@ -0,0 +1,219 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= Annotations
+
+Enable the `annotations` module if your webapp - or any of its third party libraries - uses any of the following:
+
+* Annotations:
+** @Resource
+** @Resources
+** @PostConstruct
+** @PreDestroy
+** @DeclaredRoles
+** @RunAs
+** @MultipartConfig
+** @WebServlet
+** @WebFilter
+** @WebListener
+** @WebInitParam
+** @ServletSecurity, @HttpConstraint, @HttpMethodConstraint
+** @HandlesTypes
+* javax.servlet.ServletContainerInitializers
+* JSP
+
+
+[[scanning]]
+== Annotation Scanning
+
+According to more recent versions of the Servlet Specification, the web.xml file can contain the attribute `metadata-complete`.
+If this is set to `true`, then _no_ annotation scanning takes place, and your descriptor must contain the equivalent xml statements of any annotations.
+
+If it is `metadata-complete=false`, or your web.xml predates the inclusion of this attribute, annotation scanning is required to take place.
+
+To prevent annotation scanning you can use the `WebAppContext.setConfigurationDiscovered(false)` method.
+Here's an example context xml file that calls this method:
+
+[,xml,subs=verbatim]
+----
+
+
+
+ <1>
+ false <2>
+
+----
+<1> Configures a link:{javadoc-url}/org/eclipse/jetty/webapp/WebAppContext.html[`WebAppContext`], which is the Jetty component that represents a standard Servlet web application.
+<2> Specifies that scanning should not take place.
+
+However, despite `metadata-complete=true`, scanning of classes may _still_ occur because of http://docs.oracle.com/javaee/6/api/javax/servlet/ServletContainerInitializer.html[javax.servlet.ServletContainerInitializer]s.
+Classes implementing this interface are found by Jetty using the http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html[javax.util.ServiceLoader] mechanism, and if one is present _and_ it includes the @HandlesTypes annotation, then Jetty must scan the class hierarchy of the web application.
+This may be very time-consuming if you have many jars.
+
+We will now look at ways to limit the jars that are scanned.
+
+[[og-container-include-jar-pattern]]
+=== The container classpath
+
+By default, Jetty will _not_ scan any classes that are on the container's classpath.
+
+Sometimes, you may have third party libraries on the container's classpath that you need to be scanned.
+In this case, use the `org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern` context attribute to define which container jars and class directories to scan.
+The value of this attribute is a regular expression.
+
+Here's an example from a context xml file that includes any jar whose name starts with "foo-" or "bar-", or a directory named "classes":
+
+[,xml,subs=verbatim]
+----
+
+
+
+ <1>
+ <2>
+ org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern <3>
+ .*/foo-[^/]*\.jar$|.*/bar-[^/]*\.jar$|.*/classes/.* <4>
+
+
+----
+<1> Configures a link:{javadoc-url}/org/eclipse/jetty/webapp/WebAppContext.html[`WebAppContext`], which is the Jetty component that represents a standard Servlet web application.
+<2> Specifies a context attribute.
+<3> Specifies the name of the context attribute.
+<4> Specifies the value of the context attribute.
+
+Note that the order of the patterns defines the ordering of the scanning of the jars or class directories.
+
+[[og-web-inf-include-jar-pattern]]
+=== The webapp classpath
+
+By default Jetty will scan __all__ classes from `WEB-INF/classes` and _all_ jars from `WEB-INF/lib` according to the order, if any, established by absolute or relative ordering clauses in web.xml.
+
+If your webapp contains many jar files that you know do not contain any annotations, you can significantly speed up deployment by omitting them from scanning.
+However, be careful if your webapp uses a `ServletContainerInitializer` with an `@HandlesTypes` annotation that you don't exclude jars that contain classes matching the annotation.
+
+Use the `org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern` context attribute to define a regular expression for jars and class directories to select for scanning.
+
+Here's an example of a context xml file that sets a pattern that matches any jar on the webapp's classpath that starts with `spring-`:
+
+[,xml,subs=verbatim]
+----
+
+
+
+ <1>
+ <2>
+ org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern <3>
+ .*/spring-[^/]*\.jar$ <4>
+
+
+----
+<1> Configures a link:{javadoc-url}/org/eclipse/jetty/webapp/WebAppContext.html[`WebAppContext`], which is the Jetty component that represents a standard Servlet web application.
+<2> Specifies a context attribute.
+<3> Specifies the name of the context attribute.
+<4> Specifies the value of the context attribute.
+
+=== Multi-threading
+
+By default Jetty performs annotation scanning in a multi-threaded manner in order to complete it in the minimum amount of time.
+
+If you don't want multi-threaded scanning, you can configure Jetty to revert to single-threaded scanning.
+There are several options to configure this:
+
+1. Set the context attribute `org.eclipse.jetty.annotations.multiThreaded` to `false`
+2. Set the `Server` attribute `org.eclipse.jetty.annotations.multiThreaded` to `false`
+3. Set the System property `org.eclipse.jetty.annotations.multiThreaded` to `false`
+
+Method 1 will only affect the current webapp.
+Method 2 will affect all webapps deployed to the same Server instance.
+Method 3 will affect all webapps deployed in the same JVM.
+
+By default, Jetty will wait a maximum of 60 seconds for all of the scanning threads to complete.
+You can set this to a higher or lower number of seconds by doing one of the following:
+
+1. Set the context attribute `org.eclipse.jetty.annotations.maxWait`
+2. Set the `Server` attribute `org.eclipse.jetty.annotations.maxWait`
+3. Set the System property `org.eclipse.jetty.annotations.maxWait`
+
+Method 1 will only affect the current webapp.
+Method 2 will affect all webapps deployed to the same Server instance.
+Method 3 will affect all webapps deployed in the same JVM.
+
+[[scis]]
+== ServletContainerInitializers
+
+The http://docs.oracle.com/javaee/6/api/javax/servlet/ServletContainerInitializer.html[javax.servlet.ServletContainerInitializer] class can exist in: the container's classpath, the webapp's `WEB-INF/classes` directory, the webapp's `WEB-INF/lib` jars, or any external extraClasspath that you have configured on the webapp.
+
+The Servlet Specification does not define any order in which a `ServletContainerInitializer` must be called when the webapp starts.
+By default Jetty will call them in the following order:
+
+1. ServletContainerInitializers from the container's classpath
+2. ServletContainerInitializers from WEB-INF/classes
+3. ServletContainerInitializers from WEB-INF/lib jars __in the order established in web.xml__, or in the order that the SCI is returned by the http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html[javax.util.ServiceLoader] if there is _no_ ordering.
+
+=== Exclusions
+
+By default, as according to the Servlet Specification, all `ServletContainerInitializer` that are discovered are invoked.
+
+Sometimes, depending on your requirements, you may need to prevent some being called at all.
+
+In this case, you can define the `org.eclipse.jetty.containerInitializerExclusionPattern` context attribute.
+
+This is a regular expression that defines http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html[patterns] of classnames that you want to exclude.
+Here's an example of setting the context attribute in a context xml file:
+
+[,xml,subs=verbatim]
+----
+
+
+
+ <1>
+ <2>
+ org.eclipse.jetty.containerInitializerExclusionPattern <3>
+ com.acme.*|com.corp.SlowContainerInitializer <4>
+
+
+----
+<1> Configures a link:{javadoc-url}/org/eclipse/jetty/webapp/WebAppContext.html[`WebAppContext`], which is the Jetty component that represents a standard Servlet web application.
+<2> Specifies a context attribute.
+<3> Specifies the name of the context attribute.
+<4> Specifies the value of the context attribute.
+
+In this example we exclude *all* `ServletContainerInitializer` instances in the `com.acme package`, and the specific class `com.corp.SlowContainerInitializer`.
+
+It is possible to use exclusion and ordering together to control `ServletContainerInitializer` invocation - the exclusions will be applied before the ordering.
+
+=== Ordering
+
+If you need `ServletContainerInitializer` classes called in a specific order, you can use the context attribute `org.eclipse.jetty.containerInitializerOrder`.
+Set it to a list of comma separated class names of `ServletContainerInitializers` in the order that you want them applied.
+
+You may optionally use the wildcard character `+*+` *once* in the list.
+It will match all `ServletContainerInitializer` classes not explicitly named in the list.
+
+Here is an example context xml file that ensures the `com.example.PrioritySCI` will be called first, followed by the `com.acme.FooSCI`, then all other SCIs:
+
+[,xml,subs=verbatim]
+----
+
+
+
+ <1>
+ <2>
+ org.eclipse.jetty.containerInitializerOrder <3>
+ org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServletContainerInitializer, com.acme.FooSCI, * <4>
+
+
+----
+<1> Configures a link:{javadoc-url}/org/eclipse/jetty/webapp/WebAppContext.html[`WebAppContext`], which is the Jetty component that represents a standard Servlet web application.
+<2> Specifies a context attribute.
+<3> Specifies the name of the context attribute.
+<4> Specifies the value of the context attribute.
diff --git a/documentation/jetty/modules/operations-guide/pages/arch/index.adoc b/documentation/jetty/modules/operations-guide/pages/arch/index.adoc
new file mode 100644
index 00000000000..8cb05315bb4
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/arch/index.adoc
@@ -0,0 +1,160 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= Architecture Overview
+
+Jetty is an HTTP server and Servlet Container, and supports deployments of web applications.
+
+The xref:server/index.adoc[Jetty server] listens on one or more network ports using one or more xref:protocols/index.adoc[protocol connectors].
+
+Clients send HTTP requests for specific URIs, such as `+https://host/store/cart+`.
+
+The HTTP requests arrive to the connectors through the network; the Jetty server processes the requests and, based on their URIs, forwards them to the appropriate xref:deploy/index.adoc[deployed web application].
+
+[plantuml]
+----
+skinparam backgroundColor transparent
+skinparam monochrome true
+skinparam shadowing false
+skinparam roundCorner 10
+
+scale 1.25
+
+cloud Internet as internet
+rectangle "Jetty Server" as server
+rectangle "HTTP/1.1 Connector" as http
+rectangle "HTTP/2 Connector" as http2
+rectangle "WebApp "Store"" as store
+rectangle "WebApp "Catalog"" as catalog
+
+internet -- http
+internet -- http2
+http -- server
+http2 -- server
+server -- store
+server -- catalog
+----
+
+[[concepts]]
+== Main Concepts
+There are three main concepts on which the Jetty standalone server is based:
+
+* The <>, where Jetty modules provides Jetty features.
+* The <>, that provides a place where you configure which Jetty modules you want to enable, configure the properties of each enabled module, and therefore configure the features you need for your web applications.
+* The <>, that starts a JVM that runs Jetty with the configuration you specified.
+
+After installing Jetty, you will want to set up a <> where you configure <>.
+
+[[modules]]
+== Jetty Modules
+
+The Jetty standalone server is made of Java components that are assembled together, configured and started to provide different features.
+
+A Jetty _module_ provides one or more components that work together to provide typically one feature, although they may provide more than one feature.
+
+A Jetty module is nothing more than Jetty components assembled together like you would do using Java APIs, just done in a declarative way using configuration files.
+What you can do in Java code to assemble Jetty components can be done using Jetty modules.
+
+A Jetty module may be dependent on other Jetty modules: for example, the `http` Jetty module depends on the `server` Jetty module which in turn depends on the `threadpool` and `logging` Jetty modules.
+
+Every feature in a Jetty server is enabled by enabling the corresponding Jetty module(s).
+
+For example, if you enable only the `http` Jetty module, then your Jetty standalone server will only be able to listen to a network port for clear-text HTTP requests.
+It will not be able to process secure HTTP (i.e. `https`) requests, it will not be able to process WebSocket, or HTTP/2, or HTTP/3 or any other protocol because the correspondent modules have not been enabled.
+
+You can even start a Jetty server _without_ listening on a network port -- for example because you have enabled a custom module you wrote that provides the features you need.
+
+This allows the Jetty standalone server to be as small as necessary: modules that are not enabled are not loaded, don't waste memory, and you don't risk a client using a module that you did not know was even there.
+
+For more detailed information about the Jetty module system, see xref:modules/index.adoc[this section].
+
+[[jetty-base]]
+== `$JETTY_HOME` and `$JETTY_BASE`
+
+Instead of managing multiple Jetty distributions out of many locations, it is possible to maintain a separation between the binary installation of the standalone Jetty, known as `$JETTY_HOME`, and the customizations for your specific environment(s), known as `$JETTY_BASE`.
+
+This separation between the binary installation directory and the specific configuration directory allows managing multiple, different, server configurations, and allows for quick, drop-in upgrades of Jetty.
+
+There should always only be *one* `$JETTY_HOME` (per version of Jetty), but there can be many `$JETTY_BASE` directories that reference it.
+
+This separation between `$JETTY_HOME` and `$JETTY_BASE` allows Jetty upgrades without affecting your web applications.
+`$JETTY_HOME` contains the Jetty runtime and libraries and the default configuration, while a `$JETTY_BASE` contains your web applications and any override of the default configuration.
+
+For example, with the `$JETTY_HOME` installation the default value for the network port for clear-text HTTP is `8080`.
+However, you may want that port to be `6060`, because xref:protocols/index.adoc#proxy[Jetty is behind a load balancer] that is configured to forward to the backend on port `6060`.
+In this case, you configure the clear-text HTTP port in `$JETTY_BASE`, not in `$JETTY_HOME`.
+When you upgrade Jetty, you will upgrade only the files in `$JETTY_HOME`, and all the configuration in `$JETTY_BASE` will remain unchanged, keeping your clear-text HTTP port at `6060`.
+
+Installing the Jetty runtime and libraries in `$JETTY_HOME` also allows you to leverage file system permissions: `$JETTY_HOME` may be owned by an administrator user (so that only administrators can upgrade it), while `$JETTY_BASE` directories may be owned by a less privileged user.
+
+If you had changed the default configuration in `$JETTY_HOME`, when you upgrade Jetty, say from version `10.0.0` to version `10.0.1`, your changes would be lost.
+Maintaining all the changes in `$JETTY_HOME`, and having to reconfigure these with each upgrade results in a massive commitment of time and effort.
+
+To recap:
+
+`$JETTY_HOME`::
+This is the location for the Jetty binaries.
+`$JETTY_BASE`::
+This is the location for your configurations and customizations to the Jetty binaries.
+
+[[start]]
+== Start Mechanism
+
+The Jetty start mechanism provides two features:
+
+* The mean to configure your `$JETTY_BASE` by enabling the desired modules, and to display the configuration of your `$JETTY_BASE`.
+* The mean to start Jetty itself, by starting a JVM that reads the Jetty configuration in `$JETTY_BASE`, which is then executed to assemble and start the Jetty components.
+
+The Jetty start mechanism is invoked by executing `$JETTY_HOME/start.jar` from within your `$JETTY_BASE`, and you can think of it as the Jetty command line program, similar to many Unix/Windows command line programs.
+
+For example, you can ask for help:
+
+----
+$ java -jar $JETTY_HOME/start.jar --help
+----
+
+Or you can list all available modules (or only those with a specific tag):
+
+----
+# List all the modules.
+$ java -jar $JETTY_HOME/start.jar --list-modules=*
+
+# List all the modules tagged as "demo".
+$ java -jar $JETTY_HOME/start.jar --list-modules=demo
+----
+
+You can enable a module, for example the `http` module:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=http
+----
+
+Once you have one or more module enabled, you can display the current configuration, to verify that the configuration is correct:
+
+----
+$ java -jar $JETTY_HOME/start.jar --list-config
+----
+
+You can enable a Jetty demo module, which will deploy a demo web application:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=demo-simple
+----
+
+Finally, you can start Jetty:
+
+----
+$ java -jar $JETTY_HOME/start.jar
+----
+
+Read more information at the xref:start/index.adoc[Jetty start mechanism section].
diff --git a/documentation/jetty/modules/operations-guide/pages/begin/index.adoc b/documentation/jetty/modules/operations-guide/pages/begin/index.adoc
new file mode 100644
index 00000000000..f77381fb44a
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/begin/index.adoc
@@ -0,0 +1,304 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= Getting Started
+
+If you are new to Eclipse Jetty, read on to download, install, start and deploy web applications to Jetty.
+
+== Quick Setup
+
+Jetty is distributed in an artifact that expands in a directory called `$JETTY_HOME`, which should not be modified.
+
+Configuration for Jetty is typically done in a directory called `$JETTY_BASE`.
+There may be more than one `$JETTY_BASE` directories with different configurations.
+
+The following commands can be used to set up a `$JETTY_BASE` directory that supports deployment of `+*.war+` files and a clear-text HTTP connector:
+
+----
+$ export JETTY_HOME=/path/to/jetty-home
+$ mkdir /path/to/jetty-base
+$ cd /path/to/jetty-base
+$ java -jar $JETTY_HOME/start.jar --add-module=server,http,deploy
+----
+
+The last command creates a `$JETTY_BASE/start.d/` directory and other directories that contain the configuration of the server, including the `$JETTY_BASE/webapps/` directory, in which standard `+*.war+` files can be deployed.
+
+To deploy Jetty's demo web applications, run this command:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=demo
+----
+
+Now you can start the Jetty server with:
+
+----
+$ java -jar $JETTY_HOME/start.jar
+----
+
+Point your browser at `+http://localhost:8080+` to see the demo web applications deployed in Jetty.
+
+The Jetty server can be stopped with `ctrl+c` in the terminal window.
+
+The following sections will guide you in details about <>, <> and <> Jetty, and <> your web applications to Jetty.
+
+Read the xref:arch/index.adoc[Jetty architecture section] for more information about Jetty modules, `$JETTY_HOME`, `$JETTY_BASE` and how to customize and start Jetty.
+
+[[download]]
+== Downloading Jetty
+
+The Eclipse Jetty distribution is available for download from https://eclipse.dev/jetty/download.php[]
+
+The Eclipse Jetty distribution is available in both `zip` and `gzip` formats; download the one most appropriate for your system, typically `zip` for Windows and `gzip` for other operating systems.
+
+[[install]]
+== Installing Jetty
+
+After the download, unpacking Eclipse Jetty will extract the files into a directory called `jetty-home-VERSION`, where `VERSION` is the version that you downloaded, for example `{version}`, so that the directory is called `jetty-home-{version}`.
+
+Unpack Eclipse Jetty compressed file in a convenient location, for example under `/opt`.
+
+CAUTION: For Windows users, you should unpack Jetty to a path that does not contain spaces.
+
+The rest of the instructions in this documentation will refer to this location as `$JETTY_HOME`, or `${jetty.home}`.
+
+IMPORTANT: It is important that *only* stable release versions are used in production environments.
+Versions that have been deprecated or are released as Milestones (M), Alpha, Beta or Release Candidates (RC) are *not* suitable for production as they may contain security flaws or incomplete/non-functioning feature sets.
+
+If you are new to Jetty, you should read the xref:arch/index.adoc[Jetty architecture section] to become familiar with the terms used in this documentation.
+Otherwise, you can jump to the <>.
+
+[[start]]
+== Starting Jetty
+
+Eclipse Jetty as a standalone server has no graphical user interface, so configuring and running the server is done from the command line.
+
+Recall from the xref:arch/index.adoc[architecture section] that Jetty is based on xref:modules/index.adoc[modules], that provides features, and on xref:arch/index.adoc#jetty-base[`$JETTY_BASE`], the place where you configure which module (and therefore which feature) you want to enable, and where you configure module parameters.
+
+Jetty is started by executing `$JETTY_HOME/start.jar` from within a `$JETTY_BASE` directory, so first we need to create a `$JETTY_BASE`:
+
+----
+$ JETTY_BASE=/path/to/jetty.base
+$ cd $JETTY_BASE
+----
+
+If you try to start Jetty from an empty `$JETTY_BASE` you get:
+
+----
+$ java -jar $JETTY_HOME/start.jar
+----
+
+[jetty%nowrap]
+....
+[jetty]
+....
+
+Jetty exited complaining that there are no modules enabled, since the `$JETTY_BASE` you just created is empty and therefore there is no configuration to read to assemble the Jetty server.
+
+However, it shows that `start.jar` takes parameters, whose details can be found in xref:start/index.adoc[this section].
+
+You can explore what modules are available out of the box via:
+
+----
+$ java -jar $JETTY_HOME/start.jar --list-modules=*
+----
+
+Let's try to enable the `http` module (see also xref:protocols/index.adoc#http[this section] for additional information):
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=http
+----
+
+[jetty%nowrap]
+....
+[jetty]
+args=--add-module=http
+....
+
+Now you can start Jetty:
+
+----
+$ java -jar $JETTY_HOME/start.jar
+----
+
+[jetty%nowrap]
+....
+[jetty]
+args=--module=http
+highlight=(\{.*:8080})
+....
+
+Note how Jetty is listening on port `8080` for clear-text HTTP/1.1 connections.
+
+After having enabled the `http` module, the `$JETTY_BASE` directory looks like this:
+
+[source,subs=verbatim]
+----
+JETTY_BASE
+├── resources
+│ └── jetty-logging.properties <1>
+└── start.d <2>
+ └── http.ini <3>
+----
+
+<1> The `resources/jetty-logging.properties` file has been created because the `http` modules depends on the `server` module, which in turn depends on the `logging` module; the `logging` module created this file that can be configured to control the server logging level.
+<2> The `start.d/` directory contains the configuration files for the modules.
+<3> The `start.d/http.ini` file is the `http` module configuration file, where you can specify values for the `http` module properties.
+
+In the `http.ini` file you can find the following content (among other content):
+
+.http.ini
+[source,subs=verbatim]
+----
+--module=http <1>
+# jetty.http.port=8080 <2>
+...
+----
+
+<1> This line enables the `http` module and should not be modified.
+<2> This line is commented out and specifies the default value for the module property `jetty.http.port`, which is the network port that listens for clear-text HTTP connections.
+
+You can change the module property `jetty.http.port` value directly from the command line:
+
+----
+$ java -jar $JETTY_HOME/start.jar jetty.http.port=9999
+----
+
+To make this change persistent, you can edit the `http.ini` file, uncomment the module property `jetty.http.port` and change its value to `9999`:
+
+.http.ini
+----
+--module=http
+jetty.http.port=9999
+...
+----
+
+If you restart Jetty, the new value will be used:
+
+----
+$ java -jar $JETTY_HOME/start.jar
+----
+
+[jetty%nowrap]
+....
+[jetty]
+args=--module=http jetty.http.port=9999
+highlight=(\{.*:9999})
+....
+
+Note how Jetty is now listening on port `9999` for clear-text HTTP/1.1 connections.
+
+NOTE: If you want to enable support for different protocols such as secure HTTP/1.1 or HTTP/2 or HTTP/3, or configure Jetty behind a load balancer, read xref:protocols/index.adoc[this section].
+
+The Jetty server is now up and running, but it has no web applications deployed, so it just replies with `404 Not Found` to every request.
+It is time to <> to Jetty.
+
+For more detailed information about the Jetty start mechanism, you can read the xref:arch/index.adoc#start[Jetty start mechanism] section.
+
+[[deploy]]
+== Deploying Web Applications
+
+For the purpose of deploying web applications to Jetty, there are two types of resources that can be deployed:
+
+* Standard Web Application Archives, in the form of `+*.war+` files or web application directories, defined by the Servlet specification.
+Their deployment is described in <>.
+* Jetty context XML files, that allow you to customize the deployment of standard web applications, and also allow you use Jetty components, and possibly custom components written by you, to assemble your web applications.
+Their deployment is described in xref:deploy/index.adoc[this section].
+
+[[deploy-war]]
+=== Deploying +*.war+ Files
+
+A standard Servlet web application is packaged in either a `+*.war+` file or in a directory with the structure of a `+*.war+` file.
+
+[NOTE]
+====
+Recall that the structure of a `+*.war+` file is as follows:
+
+[source,subs=verbatim]
+----
+mywebapp.war
+├── index.html <1>
+└── WEB-INF <2>
+ ├── classes/ <3>
+ ├── lib/ <4>
+ └── web.xml <5>
+----
+<1> Publicly accessible resources such as `+*.html+`, `+*.jsp+`, `+*.css+`, `+*.js+` files, etc. are placed in `+*.war+` or in sub-directories of the `+*.war+`.
+<2> `WEB-INF` is a special directory used to store anything related to the web application that must not be publicly accessible, but may be accessed by other resources.
+<3> `WEB-INF/classes` stores the web application compiled `+*.class+` files
+<4> `WEB-INF/lib` stores the web application `+*.jar+` files
+<5> `WEB-INF/web.xml` is the web application deployment descriptor defines the components and the configuration of your web application.
+====
+
+To deploy a standard web application, you need to enable the `deploy` module (see the `deploy` module complete definition xref:modules/standard.adoc#deploy[here]).
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=deploy
+----
+
+[jetty%nowrap]
+....
+[jetty]
+setupArgs=--add-module=http
+args=--add-module=deploy
+....
+
+The `deploy` module creates the `$JETTY_BASE/webapps` directory, the directory where `+*.war+` files or web application directories should be copied so that Jetty can deploy them.
+
+[NOTE]
+====
+The `deploy` module only provides the feature of deploying web applications.
+
+Whether these web applications are served via clear-text HTTP/1.1, or secure HTTP/1.1, or secure HTTP/2, or HTTP/3 (or even all of these protocols) depends on whether the correspondent Jetty modules have been enabled.
+Refer to the xref:protocols/index.adoc[section about protocols] for further information.
+====
+
+Now you need to copy a web application to the `$JETTY_BASE/webapps` directory, and you can use one of the demos shipped with Jetty:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=demo-simple
+----
+
+The `$JETTY_BASE` directory is now:
+
+----
+$JETTY_BASE
+├── resources
+│ └── jetty-logging.properties
+├── start.d
+│ ├── deploy.ini
+│ └── http.ini
+└── webapps
+ └── demo-simple.war
+----
+
+Now start Jetty:
+
+----
+$ java -jar $JETTY_HOME/start.jar
+----
+
+[jetty%nowrap]
+....
+[jetty]
+setupArgs=--add-modules=http,deploy,demo-simple
+highlight=WebAppContext
+....
+
+Note the highlighted line that logs the deployment of `demo-simple.war`.
+
+Now you can access the web application by pointing your browser to `+http://localhost:8080/demo-simple+`.
+
+[[deploy-war-advanced]]
+=== Advanced Deployment
+
+If you want to customize the deployment of your web application, for example by specifying a `contextPath` different from the file/directory name, or by specifying JNDI entries, or by specifying virtual hosts, etc. read xref:deploy/index.adoc[this section].
diff --git a/documentation/jetty/modules/operations-guide/pages/deploy/index.adoc b/documentation/jetty/modules/operations-guide/pages/deploy/index.adoc
new file mode 100644
index 00000000000..5e16df2a1a7
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/deploy/index.adoc
@@ -0,0 +1,418 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= Web Application Deployment
+
+Most of the times you want to be able to customize the deployment of your web applications, for example by changing the `contextPath`, or by adding JNDI entries, or by configuring virtual hosts, etc.
+
+The customization is performed by the xref:modules/standard.adoc#deploy[`deploy` module] by processing <>.
+
+The `deploy` module contains the `DeploymentManager` component that scans the `$JETTY_BASE/webapps` directory for changes, following the deployment rules described in <>.
+
+[[hot-static]]
+== Hot vs Static Deployment
+
+The `DeploymentManager` scans the `$JETTY_BASE/webapps` directory for changes every `N` seconds, where `N` is configured via the `jetty.deploy.scanInterval` property.
+
+By default, the scan interval is `1` second, which means that _hot_ deployment is enabled: if a file is added/changed/removed from the `$JETTY_BASE/webapps` directory, the `DeploymentManager` will notice the change and respectively deploy/redeploy/undeploy the web application.
+
+Setting the scan interval to `0` means that _static_ deployment is enabled, and the `DeploymentManager` will not scan the `$JETTY_BASE/webapps` directory for changes.
+This means that to deploy/redeploy/undeploy a web application you will need to stop and restart Jetty.
+
+The following command line disables _hot_ deployment by specifying the `jetty.deploy.scanInterval` property on the command line, and therefore only for this particular run:
+
+----
+$ java -jar $JETTY_HOME/start.jar jetty.deploy.scanInterval=0
+----
+
+To make _static_ deployment persistent, you need to edit the `deploy` module configuration file, `$JETTY_BASE/start.d/deploy.ini`, uncomment the module property `jetty.deploy.scanInterval` and change its value to `0`:
+
+.deploy.ini
+[source,subs=quotes]
+----
+--module=deploy
+#jetty.deploy.scanInterval=0#
+...
+----
+
+[[rules]]
+== Deployment Rules
+
+_Adding_ a `+*.war+` file, a `+*.war+` directory, a Jetty context XML file or a normal directory to `$JETTY_BASE/webapps` causes the `DeploymentManager` to deploy the new web application.
+
+_Updating_ a `+*.war+` file or a Jetty context XML file causes the `DeploymentManager` to redeploy the web application, which means that the Jetty context component representing the web application is stopped, then reconfigured, and then restarted.
+
+_Removing_ a `+*.war+` file, a `+*.war+` directory, a Jetty context XML file or a normal directory from `$JETTY_BASE/webapps` causes the `DeploymentManager` to undeploy the web application, which means that the Jetty context component representing the web application is stopped and removed from the Jetty server.
+
+When a file or directory is added to `$JETTY_BASE/webapps`, the `DeploymentManager` derives the web application `contextPath` from the file or directory name, with the following rules:
+
+* If the directory name is, for example, `mywebapp/`, it is deployed as a standard web application if it contains a `WEB-INF/` subdirectory, otherwise it is deployed as a web application of static content.
+The `contextPath` would be `/mywebapp` (that is, the web application is reachable at `+http://localhost:8080/mywebapp/+`).
+* If the directory name is `ROOT`, case insensitive, the `contextPath` is `/` (that is, the web application is reachable at `+http://localhost:8080/+`).
+* If the directory name ends with `.d`, for example `config.d/`, it is ignored, although it may be referenced to configure other web applications (for example to store common files).
+* If the `+*.war+` file name is, for example, `mywebapp.war`, it is deployed as a standard web application with the context path `/mywebapp` (that is, the web application is reachable at `+http://localhost:8080/mywebapp/+`).
+* If the file name is `ROOT.war`, case insensitive, the `contextPath` is `/` (that is, the web application is reachable at `+http://localhost:8080/+`).
+* If both the `mywebapp.war` file and the `mywebapp/` directory exist, only the file is deployed.
+This allows the directory with the same name to be the `+*.war+` file unpack location and avoid that the web application is deployed twice.
+* A <> named `mywebapp.xml` is deployed as a web application by processing the directives contained in the XML file itself, which must set the `contextPath`.
+* If both `mywebapp.xml` and `mywebapp.war` exist, only the XML file is deployed.
+This allows the XML file to reference the `+*.war+` file and avoid that the web application is deployed twice.
+
+[[jetty]]
+== Deploying Jetty Context XML Files
+
+A Jetty context XML file is a xref:xml/index.adoc[Jetty XML file] that allows you to customize the deployment of web applications.
+
+NOTE: Recall that the `DeploymentManager` component of the Jetty `deploy` module <> to Jetty context XML files over `+*.war+` files or directories.
+
+To deploy a web application using a Jetty context XML file, simply place the file in the `$JETTY_BASE/webapps` directory.
+
+A simple Jetty context XML file, for example named `wiki.xml` is the following:
+
+.wiki.xml
+[,xml,subs=verbatim]
+----
+
+
+
+ <1>
+ /wiki <2>
+ /opt/myapps/myapp.war <3>
+
+----
+<1> Configures a link:{javadoc-url}/org/eclipse/jetty/webapp/WebAppContext.html[`WebAppContext`], which is the Jetty component that represents a standard Servlet web application.
+<2> Specifies the web application `contextPath`, which may be different from the `+*.war+` file name.
+<3> Specifies the file system path of the `+*.war+` file.
+
+The `$JETTY_BASE` directory would look like this:
+
+----
+$JETTY_BASE
+├── resources
+│ └── jetty-logging.properties
+├── start.d
+│ ├── deploy.ini
+│ └── http.ini
+└── webapps
+ └── wiki.xml
+----
+
+TIP: The `+*.war+` file may be placed anywhere in the file system and does not need to be placed in the `$JETTY_BASE/webapps` directory.
+
+IMPORTANT: If you place both the Jetty context XML file _and_ the `+*.war+` file in the `$JETTY_BASE/webapps` directory, remember that they must have the same file name, for example `wiki.xml` and `wiki.war`, so that the `DeploymentManager` deploys the web application only once using the Jetty context XML file (and not the `+*.war+` file).
+
+You can use the features of xref:xml/index.adoc[Jetty XML files] to avoid to hard-code file system paths or other configurations in your Jetty context XML files, for example by using system properties:
+
+.wiki.xml
+[,xml]
+----
+
+
+
+
+ /wiki
+ /myapp.war
+
+----
+
+Note how the `+*.war+` file path is now obtained by resolving the system property `myapps.dir` that you can specify on the command line when you start Jetty:
+
+----
+$ java -jar $JETTY_HOME/start.jar -Dmyapps.dir=/opt/myapps
+----
+
+[[jndi]]
+== Configuring JNDI Entries
+
+A web application may _reference_ a JNDI entry, such as a JDBC `DataSource` from the web application `web.xml` file.
+The JNDI entry must be _defined_ in a xref:jndi/index.adoc#xml[Jetty XML file], for example a context XML like so:
+
+.mywebapp.xml
+[,xml,subs=normal]
+----
+
+
+
+
+ /mywebapp
+ /opt/webapps/mywebapp.war
+#
+
+ jdbc/myds
+
+
+ jdbc:mysql://localhost:3306/databasename
+ user
+ password
+
+
+ #
+
+----
+
+For more information and examples on how to use JNDI in Jetty, refer to the xref:jndi/index.adoc[JNDI] feature section.
+
+[IMPORTANT]
+====
+Class `com.mysql.cj.jdbc.MysqlConnectionPoolDataSource` is present in the MySQL JDBC driver file, `mysql-connector-java-.jar`, which must be available on the server's classpath .
+
+If the class is instead present _within_ the web application, then the JNDI entry must be declared in a `WEB-INF/jetty-env.xml` file - see the xref:jndi/index.adoc[JNDI] feature section for more information and examples.
+
+====
+
+[[virtual-hosts]]
+== Configuring Virtual Hosts
+
+A _virtual host_ is an internet domain name, registered in the Domain Name Server (DNS), for an IP address such that multiple virtual hosts will resolve to the same IP address of a single server instance.
+
+If you have multiple web applications deployed on the same Jetty server, by using virtual hosts you will be able to target a specific web application.
+
+For example, you may have a web application for your business and a web application for your hobbies , both deployed in the same Jetty server.
+By using virtual hosts, you will be able to have the first web application available at `+http://domain.biz/+`, and the second web application available at `+http://hobby.net/+`.
+
+Another typical case is when you want to use different subdomains for different web application, for example a project website is at `+http://project.org/+` and the project documentation is at `+http://docs.project.org+`.
+
+Virtual hosts can be used with any context that is a subclass of link:{javadoc-url}/org/eclipse/jetty/server/handler/ContextHandler.html[ContextHandler].
+
+[[virtual-hosts-names]]
+=== Virtual Host Names
+
+Jetty supports the following variants to be specified as virtual host names:
+
+`www.hostname.com`::
+A fully qualified domain name. It is important to list all variants as a site may receive traffic for both `www.hostname.com` and `hostname.com`.
+
+`*.hostname.com`::
+A wildcard domain name which will match only one level of arbitrary subdomains.
+*.foo.com will match www.foo.com and m.foo.com, but not www.other.foo.com.
+
+`10.0.0.2`::
+An IP address may be set as a virtual host to indicate that a web application should handle requests received on the network interface with that IP address for protocols that do not indicate a host name such as HTTP/0.9 or HTTP/1.0.
+
+`@ConnectorName`::
+A Jetty server `Connector` name to indicate that a web application should handle requests received on the server `Connector` with that name, and therefore received on a specific socket address (either an IP port for `ServerConnector`, or a Unix-Domain path for `UnixDomainServerConnector`).
+A server `Connector` name can be set via link:{javadoc-url}/org/eclipse/jetty/server/AbstractConnector.html#setName(java.lang.String)[].
+
+`www.√integral.com`::
+Non-ASCII and https://en.wikipedia.org/wiki/Internationalized_domain_name[IDN] domain names can be set as virtual hosts using https://en.wikipedia.org/wiki/Punycode[Puny Code] equivalents that may be obtained from a https://www.punycoder.com/[Punycode/IDN converters].
+For example if the non-ASCII domain name `www.√integral.com` is given to a browser, then the browser will make a request that uses the domain name `www.xn--integral-7g7d.com`, which is the name that should be added as the virtual host name.
+
+[[virtual-hosts-config]]
+=== Virtual Hosts Configuration
+
+If you have a web application `mywebapp.war` you can configure its virtual hosts in this way:
+
+[,xml]
+----
+
+
+
+
+ /mywebapp
+ /opt/webapps/mywebapp.war
+
+
+ - mywebapp.com
+ - www.mywebapp.com
+ - mywebapp.net
+ - www.mywebapp.net
+
+
+
+----
+
+Your web application will be available at:
+
+* `+http://mywebapp.com/mywebapp+`
+* `+http://www.mywebapp.com/mywebapp+`
+* `+http://mywebapp.net/mywebapp+`
+* `+http://www.mywebapp.net/mywebapp+`
+
+[NOTE]
+====
+You configured the `contextPath` of your web application to `/mywebapp`.
+
+As such, a request to `+http://mywebapp.com/other+` will not match your web application because the `contextPath` does not match.
+
+Likewise, a request to `+http://other.com/mywebapp+` will not match your web application because the virtual host does not match.
+====
+
+[[virtual-hosts-same-context]]
+=== Same Context Path, Different Virtual Hosts
+
+If you want to deploy different web applications to the same context path, typically the root context path `/`, you must use virtual hosts to differentiate among web applications.
+
+You have `domain.war` that you want to deploy at `+http://domain.biz/+` and `hobby.war` that you want to deploy at `+http://hobby.net+`.
+
+To achieve this, you simply use the same context path of `/` for each of your webapps, while specifying different virtual hosts for each of your webapps:
+
+.domain.xml
+[,xml]
+----
+
+
+
+
+ /
+ /opt/webapps/domain.war
+
+
+ - domain.biz
+
+
+
+----
+
+.hobby.xml
+[,xml]
+----
+
+
+
+
+ /
+ /opt/webapps/hobby.war
+
+
+ - hobby.net
+
+
+
+----
+
+[[virtual-hosts-port]]
+=== Different Port, Different Web Application
+
+Sometimes it is required to serve different web applications from different socket addresses (either different IP ports, or different Unix-Domain paths), and therefore from different server ``Connector``s.
+
+For example, you want requests to `+http://localhost:8080/+` to be served by one web application, but requests to `+http://localhost:9090/+` to be served by another web application.
+
+This configuration may be useful when Jetty sits behind a load balancer.
+
+In this case, you want to xref:protocols/index.adoc[configure multiple connectors], each with a different name, and then reference the connector name in the web application virtual host configuration:
+
+.domain.xml
+[,xml,highlight=10]
+----
+
+
+
+
+ /
+ /opt/webapps/domain.war
+
+
+ - @port8080
+
+
+
+----
+
+.hobby.xml
+[,xml,highlight=10]
+----
+
+
+
+
+ /
+ /opt/webapps/hobby.war
+
+
+ - @port9090
+
+
+
+----
+
+[NOTE]
+====
+Web application `domain.war` has a virtual host of `@port8080`, where `port8080` is the name of a Jetty connector.
+
+Likewise, web application `hobby.war` has a virtual host of `@port9090`, where `port9090` is the name of another Jetty connector.
+
+See xref:protocols/index.adoc[this section] for further information about how to configure connectors.
+====
+
+[[extract-war]]
+== Configuring `+*.war+` File Extraction
+
+By default, `+*.war+` files are uncompressed and its content extracted in a temporary directory.
+// TODO: reference the `work` module and how it works, perhaps in a section about the `deploy` module?
+The web application resources are served by Jetty from the files extracted in the temporary directory, not from the files within the `+*.war+` file, for performance reasons.
+
+If you do not want Jetty to extract the `+*.war+` files, you can disable this feature, for example:
+
+.mywebapp.xml
+[,xml,highlight=8]
+----
+
+
+
+
+ /mywebapp
+ /opt/webapps/mywebapp.war
+ false
+
+----
+
+[[jetty-override-web-xml]]
+== Overriding `web.xml`
+
+You can configure an additional `web.xml` that complements the `web.xml` file that is present in the web application `+*.war+` file.
+This additional `web.xml` is processed _after_ the `+*.war+` file `web.xml`.
+This allows you to add host specific configuration or server specific configuration without having to extract the web application `web.xml`, modify it, and repackage it in the `+*.war+` file.
+
+.mywebapp.xml
+[,xml,highlight=8]
+----
+
+
+
+
+ /mywebapp
+ /opt/webapps/mywebapp.war
+ /opt/webapps/mywebapp-web.xml
+
+----
+
+The format of the additional `web.xml` is exactly the same as a standard `web.xml` file, for example:
+
+.mywebapp-web.xml
+[,xml,linenums,highlight=10-11]
+----
+
+
+
+ my-servlet
+
+ host
+ 192.168.0.13
+
+
+
+----
+
+In the example above, you configured the `my-servlet` Servlet (defined in the web application `web.xml`), adding a host specific `init-param` with the IP address of the host.
+
+
+// TODO: move this section to its own file
+// TODO: configuring from the Jetty context XML file happens before web.xml
+// What about jetty-web.xml? Can this be specified externally, e.g. WebAppContext.setJettyWebXml() ?
+[[init-params]]
+== Configuring ``init-param``s
+
+TODO
diff --git a/documentation/jetty/modules/operations-guide/pages/features/index.adoc b/documentation/jetty/modules/operations-guide/pages/features/index.adoc
new file mode 100644
index 00000000000..083fe540aa3
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/features/index.adoc
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= Eclipse Jetty Features
+
+If you know Eclipse Jetty already, jump to a feature:
+
+Protocols::
+* xref:protocols/index.adoc#http[HTTP/1.1 Support]
+* xref:protocols/index.adoc#http2[HTTP/2 Support]
+* xref:protocols/index.adoc#http3[HTTP/3 Support]
+* xref:protocols/index.adoc#websocket[WebSocket Support]
+
+Technologies::
+* xref:annotations/index.adoc[Servlet Annotations]
+* xref:jaas/index.adoc[JAAS]
+* xref:jndi/index.adoc[JNDI]
+* xref:jsp/index.adoc[JSP]
+* xref:jmx/index.adoc[JMX Monitoring & Management]
+
+Clustering::
+* xref:session/index.adoc[HTTP Session Caching and Clustering]
+
+Performance::
+* xref:server/index.adoc#threadpool-virtual[Virtual Threads]
+* xref:quickstart/index.adoc[Faster Web Application Deployment]
diff --git a/documentation/jetty/modules/operations-guide/pages/howtos/index.adoc b/documentation/jetty/modules/operations-guide/pages/howtos/index.adoc
new file mode 100644
index 00000000000..49d7b9cbb9c
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/howtos/index.adoc
@@ -0,0 +1,24 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= Eclipse Jetty How-Tos
+
+* xref:protocols/index.adoc#http[Configure Clear-Text HTTP/1.1]
+* xref:protocols/index.adoc#https[Configure Secure HTTP/1.1 (https)]
+* xref:protocols/index.adoc#http2c[Configure Clear-Text HTTP/2]
+* xref:protocols/index.adoc#http2s[Configure Secure HTTP/2]
+* xref:protocols/index.adoc#http3[Configure HTTP/3]
+* xref:protocols/index.adoc#proxy[Configure Jetty Behind a Load Balancer or Reverse Proxy]
+* xref:server/index.adoc#logging[Configure Jetty Logging]
+* xref:server/index.adoc#threadpool[Configure Jetty Thread Pool and Virtual Threads]
+* xref:troubleshooting/index.adoc[Troubleshooting]
diff --git a/documentation/jetty/modules/operations-guide/pages/index.adoc b/documentation/jetty/modules/operations-guide/pages/index.adoc
new file mode 100644
index 00000000000..6f97667b88a
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/index.adoc
@@ -0,0 +1,17 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+[reftext=Operations Guide]
+= Jetty {page-version} Operations Guide
+
+The Eclipse Jetty Operations Guide targets sysops, devops, and developers who want to install Eclipse Jetty as a standalone server to deploy web applications.
diff --git a/documentation/jetty/modules/operations-guide/pages/jaas/index.adoc b/documentation/jetty/modules/operations-guide/pages/jaas/index.adoc
new file mode 100644
index 00000000000..c39fb251cc7
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/jaas/index.adoc
@@ -0,0 +1,333 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= JAAS
+
+JAAS implements a Java version of the standard Pluggable Authentication Module (PAM) framework.
+
+JAAS can be used for two purposes:
+
+* for authentication of users, to reliably and securely determine who is currently executing Java code, regardless of whether the code is running as an application, an applet, a bean, or a servlet
+* for authorization of users to ensure they have the access control rights (permissions) required to do the actions performed
+
+JAAS authentication is performed in a pluggable fashion.
+This permits applications to remain independent from underlying authentication technologies.
+New or updated authentication technologies can be plugged under an application without requiring modifications to the application itself.
+
+See Java Authentication and Authorization Service (JAAS) http://java.sun.com/javase/7/docs/technotes/guides/security/jaas/JAASRefGuide.html[Reference Guide] for more information about JAAS.
+
+The Jetty JAAS support aims to dictate as little as possible whilst providing a sufficiently flexible infrastructure to allow users to drop either one of the <>, or their
+own custom https://docs.oracle.com/javase/7/docs/api/javax/security/auth/spi/LoginModule.html[LoginModule]s.
+
+[[configuration]]
+== Configuration
+
+[[module]]
+=== The `jaas` module
+
+Enable the `jaas` module:
+
+----
+include::{jetty-home}/modules/jaas.mod[]
+----
+
+The configurable items in the resulting `$jetty.base/start.d/jaas.ini` file are:
+
+jetty.jaas.login.conf::
+This is the location of the file that will be referenced by the system property `java.security.auth.login.config`: Jetty sets this system property for you based on the value of this property.
+The value of this property is assumed to be _relative to ``$JETTY_BASE``_.
+The default value is `etc/login.conf`, which resolves to `$JETTY_BASE/etc/login.conf`.
+If you don't want to put your login module configuration file here, you can change this property to point to where it is.
+
+See more about the contents of this file in the <> section.
+
+[[webapp]]
+=== Configure the webapp for JAAS
+
+The `` in `web.xml` will be used to identify the `org.eclipse.jetty.jaas.JAASLoginService` declaration that integrates JAAS with Jetty.
+
+For example, this `web.xml` contains a realm called `Test JAAS Realm`:
+
+[,xml,subs=verbatim]
+----
+
+ FORM
+ Test JAAS Realm
+
+ /login/login
+ /login/error
+
+
+----
+<1> The name of the realm, which must be _identical_ to the name of an `org.eclipse.jetty.jaas.JAASLoginService` declaration.
+
+We now need to declare an `org.eclipse.jetty.jaas.JAASLoginService` that references the realm name of `Test JAAS Realm`.
+Here's an example of a suitable XML snippet:
+
+[,xml,subs=verbatim]
+----
+
+ Test JAAS Realm
+ xyz
+
+----
+<1> The name is the _same_ as that declared in the `` in `web.xml`.
+<2> The name that identifies a set of `javax.security.auth.spi.LoginModule` configurations that comprise the <> identified in the `jetty.jaas.login.conf` property of the <>.
+
+The `org.eclipse.jetty.jaas.JAASLoginService` can be declared in a couple of different places, pick whichever suits your purposes best:
+
+* If you have more than one webapp that you would like to use the same security infrastructure, then you can declare your `org.eclipse.jetty.jaas.JAASLoginService` as a bean that is added to the `org.eclipse.jetty.server.Server`.
+The file in which you declare this needs to be on Jetty's execution path.
+The recommended procedure is to create a file in your `$jetty.base/etc` directory and then ensure it is on the classpath either by adding it to the Jetty xref:start/index.adoc[start command line], or more conveniently to a xref:modules/custom.adoc[custom module].
++
+Here's an example of this type of XML file:
++
+[,xml]
+----
+
+
+
+
+
+
+ Test JAAS Realm
+ xyz
+
+
+
+
+----
+
+* Alternatively, if you want to use JAAS with a specific webapp only, you declare your `org.eclipse.jetty.jaas.JAASLoginService` in a context XLM file specific to that webapp:
++
+[,xml]
+----
+
+
+
+
+
+
+
+ Test JAAS Realm
+ xyz
+
+
+
+
+
+----
+
+[[loginconf]]
+=== Configure JAAS
+
+We now need to setup the contents of the file we specified as the `jetty.jaas.login.conf` property when we <>.
+Refer to the https://docs.oracle.com/javase/7/docs/api/javax/security/auth/login/Configuration.html[syntax rules] of this file for a full description.
+
+Remembering the example we set up <>, the contents of the `$jetty.base/etc/login.conf` file could look as follows:
+
+[source,subs=verbatim]
+----
+xyz { <1>
+ com.acme.SomeLoginModule required debug=true; <2>
+ com.other.OtherLoginModule optional; <3>
+};
+----
+<1> The name of the configuration _exactly_ as specified in your `org.eclipse.jetty.jaas.JAASLoginService` declaration.
+<2> The first `LoginModule` declaration, containing the classname of the `LoginModule` and its configuration properties.
+<3> A second `LoginModule` declaration.
+You can provide as many `LoginModule` alternatives as you like, with a minimum of one.
+Refer to the https://docs.oracle.com/javase/7/docs/api/javax/security/auth/login/Configuration.html[JAAS documentation] for more information on the standard configuration properties, and how JAAS interprets this file.
+
+[[loginmodules]]
+== Provided LoginModules
+
+* link:{javadoc-url}/org/eclipse/jetty/jaas/spi/JDBCLoginModule.html[`org.eclipse.jetty.jaas.spi.JDBCLoginModule`]
+* link:{javadoc-url}/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.html[`org.eclipse.jetty.jaas.spi.PropertyFileLoginModule`]
+* link:{javadoc-url}/org/eclipse/jetty/jaas/spi/DataSourceLoginModule.html[`org.eclipse.jetty.jaas.spi.DataSourceLoginModule`]
+* link:{javadoc-url}/org/eclipse/jetty/jaas/spi/LdapLoginModule.html[`org.eclipse.jetty.jaas.ldap.LdapLoginModule`]
+
+[[og-password]]
+[NOTE]
+====
+Passwords can be stored in clear text, obfuscated or checksummed.
+The class link:{javadoc-url}/org/eclipse/jetty/util/security/Password.html[`org.eclipse.jetty.util.security.Password`] should be used to generate all varieties of passwords,the output from which can be put in to property files or entered into database tables.
+====
+
+=== JDBCLoginModule
+
+The `org.eclipse.jetty.jaas.spi.JDBCLoginModule` stores user passwords and roles in a database accessed via JDBC calls.
+You can configure the JDBC connection information, as well as the names of the table and columns storing the username and credential, and the names of the table and columns storing the roles.
+
+Here is an example <> entry for it using an HSQLDB driver:
+
+[source,subs=verbatim]
+----
+jdbc { <1>
+ org.eclipse.jetty.jaas.spi.JDBCLoginModule required <2><3>
+ dbUrl="jdbc:hsqldb:." <4>
+ dbUserName="sa" <5>
+ dbDriver="org.hsqldb.jdbcDriver" <6>
+ userTable="myusers" <7>
+ userField="myuser" <8>
+ credentialField="mypassword" <9>
+ userRoleTable="myuserroles" <10>
+ userRoleUserField="myuser" <11>
+ userRoleRoleField="myrole"; <12>
+};
+----
+<1> The name of the configuration.
+<2> The name of the `LoginModule` class.
+<3> A standard JAAS flag making successful authentication via this `LoginModule` mandatory.
+<4> The JDBC url used to connect to the database.
+<5> The name of the JDBC user to use for the connection.
+<6> The name of the JDBC Driver class.
+<7> The name of the table holding the user authenication information.
+<8> The name of the column holding the user name.
+<9> The name of the column holding the user credential.
+<10> The name of the table holding the user authorization information.
+<11> The name of the column holding the user name.
+<12> The name of the column holding the user role.
+
+The properties *7*-*12* are used to format the following queries:
+
+[,sql]
+----
+select from where =?
+select from where =?
+----
+
+Credential and role information is lazily read from the database when a previously unauthenticated user requests authentication.
+Note that this information is _only_ cached for the length of the authenticated session.
+When the user logs out or the session expires, the information is flushed from memory.
+
+Note that passwords can be stored in the database in plain text or encoded formats -- see the note on "Passwords/Credentials" above.
+
+=== DataSourceLoginModule
+
+Similar to the `org.eclipse.jetty.jaas.spi.JDBCLoginModule`, but using a `javax.sql.DataSource` to connect to the database instead of a JDBC driver.
+The `javax.sql.DataSource` is obtained at runtime by performing a JNDI lookup on `java:comp/env/$\{dnJNDIName}`.
+
+A sample login module configuration for this `LoginModule`:
+
+[source,subs=verbatim]
+----
+ds { <1>
+ org.eclipse.jetty.jaas.spi.DataSourceLoginModule required <2><3>
+ dbJNDIName="ds" <4>
+ userTable="myusers" <5>
+ userField="myuser" <6>
+ credentialField="mypassword" <7>
+ userRoleTable="myuserroles" <8>
+ userRoleUserField="myuser" <9>
+ userRoleRoleField="myrole"; <10>
+};
+----
+<1> The name of the configuration.
+<2> The name of the `LoginModule` class.
+<3> A standard JAAS flag making successful authentication via this `LoginModule` mandatory.
+<4> The JNDI name, relative to `java:comp/env/` to lookup to obtain the `javax.sql.DataSource`.
+<5> The name of the table holding the user authenication information.
+<6> The name of the column holding the user name.
+<7> The name of the column holding the user credential.
+<8> The name of the table holding the user authorization information.
+<9> The name of the column holding the user name.
+<10> The name of the column holding the user role.
+
+=== PropertyFileLoginModule
+
+With this login module implementation, the authentication and role information is read from a property file.
+
+[source,subs=verbatim]
+----
+props { <1>
+ org.eclipse.jetty.jaas.spi.PropertyFileLoginModule required <2><3>
+ file="/somewhere/somefile.props"; <4>
+};
+----
+<1> The name of the configuration.
+<2> The name of the `LoginModule` class.
+<3> A standard JAAS flag making successful authentication via this `LoginModule` mandatory.
+<4> The location of a properties file containing the authentication and authorization information.
+
+The property file must be of the format:
+
+[,text,subs=verbatim]
+----
+: [, ...]
+----
+
+Here's an example:
+
+----
+fred: OBF:1xmk1w261u9r1w1c1xmq,user,admin
+harry: changeme,user,developer
+tom: MD5:164c88b302622e17050af52c89945d44,user
+dick: CRYPT:adpexzg3FUZAk,admin
+----
+
+The contents of the file are fully read in and cached in memory the first time a user requests authentication.
+
+=== LdapLoginModule
+
+The `org.eclipse.jetty.jaas.spi.LdapLoginModule` uses LDAP to access authentication and authorization information stored in a directory.
+The LDAP connection information and structure of the authentication/authorization data can be configured.
+
+Here's an example:
+
+[source,subs=verbatim]
+----
+example { <1>
+ org.eclipse.jetty.jaas.spi.LdapLoginModule required <2><3>
+ contextFactory="com.sun.jndi.ldap.LdapCtxFactory" <4>
+ hostname="ldap.example.com" <5>
+ port="389" <6>
+ bindDn="cn=Directory Manager" <7>
+ bindPassword="directory" <8>
+ authenticationMethod="simple" <9>
+ useLdaps="true" <10>
+ userBaseDn="ou=people,dc=alcatel" <11>
+ userRdnAttribute="uid" <12>
+ userIdAttribute="cn" <13>
+ userPasswordAttribute="userPassword" <14>
+ userObjectClass="inetOrgPerson" <15>
+ roleBaseDn="ou=groups,dc=example,dc=com" <16>
+ roleNameAttribute="cn" <17>
+ roleMemberAttribute="uniqueMember" <18>
+ roleObjectClass="groupOfUniqueNames"; <19>
+ forceBindingLogin="false" <20>
+ debug="false" <21>
+};
+----
+<1> The name of the configuration.
+<2> The name of the `LoginModule` class.
+<3> A standard JAAS flag making successful authentication via this `LoginModule` mandatory.
+<4> The name of the context factory to use for the LDAP connection.
+<5> The hostname for the LDAP connection. Optional.
+<6> The port for the LDAP connection. Optional.
+<7> The caller security Principal. Optional.
+<8> The caller security credential. Optional.
+<9> The security level for the LDAP connection environment. Optional.
+<10> If true, use `ldaps` instead of `ldap` for the connection url.
+<11> The distinguished name of the directory to search for user information.
+<12> The name of the attribute for the user roles.
+<13> The name of the attribute for the user id.
+<14> The name of the attribute for the user password.
+<15> The `ObjectClass` for users.
+<16> The distinguished name of the directory to search for role information.
+<17> The name of the attribute for roles.
+<18> The name of the attribute storing the user for the roles `ObjectClass`.
+<19> The name of the `ObjectClass` for roles.
+<20> If true, the authentication proceeds on the basis of a successful LDAP binding using the username and credential provided by the user.
+If false, then authentication proceeds based on username and password information retrieved from LDAP.
+<21> If true, failed login attempts are logged on the server.
diff --git a/documentation/jetty/modules/operations-guide/pages/jaspi/index.adoc b/documentation/jetty/modules/operations-guide/pages/jaspi/index.adoc
new file mode 100644
index 00000000000..854cffb26de
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/jaspi/index.adoc
@@ -0,0 +1,67 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= JASPI
+
+Enabling this module allows Jetty to utilize authentication modules that implement the JSR 196 (JASPI) specification. JASPI provides an SPI (Service Provider Interface) for pluggable, portable, and standardized authentication modules. Compatible modules are portable between servers that support the JASPI specification. This module provides a bridge from Java Authentication to the Jetty Security framework.
+
+Only modules conforming to the "Servlet Container Profile" with the ServerAuthModule interface within the https://www.jcp.org/en/jsr/detail?id=196[JASPI Spec] are supported. These modules must be configured before start-up. Operations for runtime registering or de-registering authentication modules are not supported.
+
+[[configuration]]
+== Configuration
+
+[[module]]
+=== The `jaspi` module
+
+Enable the `jaspi` module:
+
+----
+include::{jetty-home}/modules/jaspi.mod[]
+----
+
+[[xml]]
+=== Configure JASPI
+
+To enable the `jaspi` module you can use the following command (issued from within the `$JETTY_BASE` directory):
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=jaspi
+----
+
+You can then register a `AuthConfigProvider` onto the static `AuthConfigFactory` obtained with `AuthConfigFactory.getFactory()`. This registration can be done in the XML configuration file which will be copied to `$JETTY_BASE/etc/jaspi/jaspi-authmoduleconfig.xml` when the module is enabled.
+
+==== JASPI Demo
+The `jaspi-demo` module illustrates setting up HTTP Basic Authentication using a Java Authentication module that comes packaged with jetty: `org.eclipse.jetty.security.jaspi.modules.BasicAuthenticationAuthModule`, and applies it for a context named `/test`.
+
+[, xml]
+----
+include::{jetty-home}/etc/jaspi/jaspi-demo.xml[]
+----
+
+This example uses the `AuthConfigProvider` implementation provided by Jetty to register a `ServerAuthModule` directly. Other custom or 3rd party modules that are compatible with the `ServerAuthModule` interface in JASPI can be registered in the same way.
+
+=== Integration with Jetty Authentication Mechanisms
+
+To integrate with Jetty authentication mechanisms you must add a `LoginService` to your context. The `LoginService` provides a way for you to obtain a `UserIdentity` from a username and credentials. JASPI can interact with this Jetty `LoginService` by using the `PasswordValidationCallback`.
+
+The `CallerPrincipalCallback` and `GroupPrincipalCallback` do not require use of a Jetty `LoginService`. The principal from the `CallerPrincipalCallback` will be used directly with the `IdentityService` to produce a `UserIdentity`.
+
+=== Replacing the Jetty DefaultAuthConfigFactory
+
+Jetty provides an implementation of the `AuthConfigFactory` interface which is used to register `AuthConfigProviders`. This can be replaced by a custom implementation by adding a custom module which provides `auth-config-factory`.
+This custom module must reference an XML file which sets a new instance of the `AuthConfigFactory` with the static method `AuthConfigFactory.setFactory()`.
+For an example of this see the `jaspi-default-auth-config-factory` module, which provides the default implementation used by Jetty.
+
+----
+include::{jetty-home}/modules/jaspi-default-auth-config-factory.mod[]
+----
diff --git a/documentation/jetty/modules/operations-guide/pages/jmx/index.adoc b/documentation/jetty/modules/operations-guide/pages/jmx/index.adoc
new file mode 100644
index 00000000000..83163240555
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/jmx/index.adoc
@@ -0,0 +1,281 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= JMX Monitoring & Management
+
+Monitoring and management of a Jetty server is important because it allows you to monitor the status of the server (_"Is the server processing requests?"_) and to manage -- i.e. read and possibly change -- its configuration.
+
+The ability to read and change the Jetty configuration is very important for troubleshooting Jetty -- please refer to the xref:troubleshooting/index.adoc[troubleshooting section] for more information.
+
+Jetty relies on the Java Management Extensions (JMX) APIs included in OpenJDK to provide monitoring and management.
+
+The JMX APIs support a JVM-local `MBeanServer`, accessible only from within the JVM itself (or by tools that can _attach_ to a running JVM), and a way to expose the `MBeanServer` to remote clients via Java's RMI (Remote Method Invocation).
+
+[[local]]
+== Enabling Local JMX Support
+
+As with many other Jetty features, local JMX support is enabled with the `jmx` Jetty module:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=jmx
+----
+
+With the `jmx` Jetty module enabled, Jetty components will be exported as JMX _MBeans_ to the JVM platform `MBeanServer`, so that they can be accessed by JMX compliant tools.
+
+Each Jetty component will export to its correspondent MBean relevant configuration parameters, so that a JMX tool can read and possibly change the component configuration through the MBean.
+
+Note that the Jetty MBeans are registered into the platform `MBeanServer`, but are not available to remote clients: they are _local_ to the JVM.
+
+This configuration is useful when you develop and test your Jetty server locally.
+
+JMX compliant tools such as https://adoptium.net/jmc.html[Java Mission Control (JMC)] can be started locally on your machine and can attach to other JVMs running on your machine, showing you the registered MBeans among which you will find the Jetty MBeans.
+
+NOTE: Enabling only the local JMX support is the most secure option for monitoring and management, but only users that have local access to the JVM will be able to browse the MBeans.
+If you need to access the MBeans from a remote machine, read <>.
+
+[[remote]]
+== Enabling Remote JMX Support
+
+There are two ways to configure a Jetty server so that it is possible to access the JVM platform MBeans from remote clients:
+
+* Use the `com.sun.management.jmxremote` and related system properties when starting Jetty.
+Unfortunately, this solution does not work well with firewalls, and will not be discussed further.
+* Use the `jmx-remote` Jetty module.
+
+Both ways use Java's Remote Method Invocation (RMI) to communicate between the client and the server.
+
+[IMPORTANT]
+.Refresher: How RMI Works
+====
+A server application that wants to make an object available to remote clients must _export_ the object.
+
+Exporting an object creates an RMI _stub_ that contains the host/port of the RMI _server_ that accepts incoming invocations from clients and forwards them to the object.
+During the creation of the RMI stub, the host stored in the RMI stub is retrieved from the local name resolution system (for example, in Linux, from `/etc/hosts`).
+
+The RMI stub is then sent, along with a name that uniquely identifies the object, to the RMI _registry_.
+The RMI registry is a service that maps names to RMI stubs; it may be external to both clients and server, although often it is part of the server JVM.
+
+When a client application wants to connect to the server object using RMI, it first connects to the RMI registry to download the RMI stub for the RMI server; recall that the RMI stub contains the host/port to connect to the RMI server.
+Then, the client uses the RMI stub to connect to the RMI server, typically to a host/port that may be different from the RMI registry host/port (in particular, by default the RMI server port will be different from the RMI registry port).
+====
+
+Remote access to the platform MBeans, and therefore the Jetty MBeans, is enabled by the `jmx-remote` Jetty module:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=jmx-remote
+----
+
+This command creates the `jmx-remote.ini` file:
+
+[source,subs=quotes]
+----
+JETTY_BASE
+└── start.d
+ └── #jmx-remote.ini#
+----
+
+Enabling the `jmx-remote` module transitively enables the <> as well.
+
+The configuration for the RMI registry and the RMI server is specified by a `JMXServiceURL`.
+The string format of an RMI `JMXServiceURL` is the following:
+
+----
+service:jmx:rmi://:/jndi/rmi://:/jmxrmi
+----
+
+Below you can find examples of ``JMXServiceURL``s:
+
+[source,subs=quotes]
+----
+*service:jmx:rmi:///jndi/rmi:///jmxrmi*
+where:
+ rmi_server_host = local host address
+ rmi_server_port = randomly chosen
+ rmi_registry_host = local host address
+ rmi_registry_port = 1099
+
+*service:jmx:rmi://0.0.0.0:1099/jndi/rmi://0.0.0.0:1099/jmxrmi*
+where:
+ rmi_server_host = any address
+ rmi_server_port = 1099
+ rmi_registry_host = any address
+ rmi_registry_port = 1099
+
+*service:jmx:rmi://localhost:1100/jndi/rmi://localhost:1099/jmxrmi*
+where:
+ rmi_server_host = loopback address
+ rmi_server_port = 1100
+ rmi_registry_host = loopback address
+ rmi_registry_port = 1099
+----
+
+The default `JMXServiceURL` configured by the `jmx-remote` module is the following:
+
+----
+service:jmx:rmi://localhost:1099/jndi/rmi://localhost:1099/jmxrmi
+----
+
+With the default configuration, only clients that are local to the server machine can connect to the RMI registry and RMI server - this is done for security reasons.
+However, even with this local-only configuration, it would still be possible to access the MBeans from remote using an SSH tunnel, as explained in <>.
+
+By specifying an appropriate `JMXServiceURL`, you can fine tune the network address the RMI registry and the RMI server bind to, and the ports that the RMI registry and the RMI server listen to.
+The RMI server and RMI registry hosts and ports can be the same (as in the default configuration) because RMI is able to multiplex traffic arriving to one port to multiple RMI objects.
+
+If you need to allow JMX remote access through a firewall, you must open both the RMI registry and the RMI server ports.
+The default configuration simplifies the firewall configuration because you only need to open port `1099`.
+
+[NOTE]
+====
+When Jetty is started with the `jmx-remote` module enabled, the RMI stub of the Jetty component that provides access to the MBeans is exported to the RMI registry.
+
+The RMI stub contains the host/port to connect to the RMI server, but the host is typically the machine host name, not the host specified in the `JMXServiceURL` (the latter is only used to specify the network address the RMI server binds to).
+
+To control the host stored in the RMI stub you need to set the system property `java.rmi.server.hostname` with the desired value in the module configuration file, `jmx-remote.ini`.
+====
+
+IMPORTANT: If your client cannot connect to the server, the most common cause is a mismatch between the RMI server host of the `JMXServiceURL` and the RMI server host of the RMI stub.
+
+You can customize the RMI server host/port, the RMI registry host/port and the system property `java.rmi.server.hostname` by editing the `jmx-remote.ini` configuration file.
+Further information about the `jmx-remote` module configuration can be found xref:modules/standard.adoc#jmx-remote[here].
+
+[[remote-ssh-tunnel]]
+=== Remote JMX Access with Port Forwarding via SSH Tunnel
+
+You can access JMX MBeans on a remote machine when the RMI ports are not open, for example because of firewall policies, but you have SSH access to the machine, using local port forwarding via an SSH tunnel.
+
+In this case you want to configure the `JMXServiceURL` that binds the RMI server and the RMI registry to the loopback interface only and to the same port:
+
+----
+service:jmx:rmi://localhost:1099/jndi/rmi://localhost:1099/jmxrmi
+----
+
+You must set the system property `-Djava.rmi.server.hostname=localhost` so that the RMI stub contains `localhost` as the host name to connect to.
+This is, incidentally, the default configuration of the `jmx-remote` module.
+
+Then you set up the local port forwarding with the SSH tunnel:
+
+----
+$ ssh -L 1099:localhost:1099 @
+----
+
+Thanks to the local port forwarding of the SSH tunnel, when the client connects to `localhost:1099` on your local computer, the traffic will be forwarded to `machine_host` and when there, the SSH daemon will forward the traffic to `localhost:1099` on `machine_host`, which is exactly where the RMI server and the RMI registry listens to.
+
+The client first contacts the RMI registry, so it connects to `localhost:1099` on your local computer; the traffic is forwarded to `machine_host` through the SSH tunnel, connects to the RMI registry and the RMI stub is downloaded to the client.
+
+Then the client uses the RMI stub to connect to the RMI server. The RMI stub contains `localhost` as the RMI server host because that is what you have configured with the system property `java.rmi.server.hostname`.
+
+The client will connect again to `localhost:1099` on your local computer, this time to contact the RMI server; the traffic is forwarded to `machine_host` through the SSH tunnel, arrives to `machine_host` and connects to the RMI server.
+
+[[remote-auth]]
+=== Remote JMX Access Authentication & Authorization
+
+The standard `javax.management.remote.JMXConnectorServer` class, used by the `jmx-remote` module to provide remote JMX access to Jetty MBeans, provides several options to authenticate and authorize users.
+For a complete guide to controlling authentication and authorization in JMX, see https://docs.oracle.com/en/java/javase/11/management/monitoring-and-management-using-jmx-technology.html[the official JMX documentation].
+
+The simplest way to control JMX authentication and authorization is to specify two files: one contains username and password pairs, and the other contains username and permission pairs.
+
+This is achieved by enabling the `jmx-remote-auth` Jetty module:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=jmx-remote-auth
+----
+
+Enabling the `jmx-remote-auth` Jetty module creates the following files:
+
+----
+$JETTY_BASE
+├── etc
+│ ├── jmxremote.access
+│ ├── jmxremote.password
+│ └── jmx-remote-auth.xml
+└── start.d
+ ├── jmx-remote-auth.ini
+ └── jmx-remote.ini
+----
+
+Then you edit the `$JETTY_BASE/etc/jmxremote.password` file, adding the username/password pairs that you need:
+
+.$JETTY_BASE/etc/jmxremote.password
+----
+# The file format is:
+alice wonderland
+bob marley
+----
+
+You must also edit the `$JETTY_BASE/etc/jmxremote.access` file to give permissions to your users:
+
+.$JETTY_BASE/etc/jmxremote.access
+----
+# The file format is:
+alice readwrite
+bob readonly
+----
+
+The above files define user `alice` with password `wonderland` to have `readwrite` access, and user `bob` with password `marley` to have `readonly` access.
+
+[[remote-secure]]
+=== Securing Remote JMX Access with TLS
+
+The JMX communication via RMI happens by default in clear-text, but it is possible to secure the JMX communication via RMI with TLS.
+
+If you want to reuse the configuration that you are using for the xref:protocols/index.adoc#https[`https` module], you can just enable the `jmx-remote-ssl.xml` Jetty module:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=jmx-remote-ssl
+----
+
+[NOTE]
+====
+The `jmx-remote-ssl` Jetty module depends on the `ssl` Jetty module that in turn requires a KeyStore (read xref:protocols/index.adoc#ssl[this section] for more information).
+====
+
+The KeyStore must contain a valid certificate signed by a Certification Authority.
+Having certificates signed by a Certification Authority simplifies by a lot the configuration needed to get the RMI communication over TLS working properly.
+
+The RMI mechanic is the usual one: the RMI client (typically a monitoring console) will connect first to the RMI registry (using TLS), download the RMI stub that contains the address and port of the RMI server to connect to, then connect to the RMI server (using TLS).
+
+This also mean that if the RMI registry and the RMI server are on different hosts, the RMI client must have available the cryptographic material to validate the certificates from both hosts.
+This is where having certificates signed by a Certification Authority simplifies the configuration: if they are signed by a well known Certification Authority, the client does not need any extra configuration -- everything will be handled by the Java runtime.
+
+If the certificates are not signed by a Certification Authority (for example the certificate is self-signed), then you need to specify the TLS system properties that allow RMI (especially when acting as an RMI client) to retrieve the cryptographic material necessary to establish the TLS connection.
+
+[IMPORTANT]
+====
+When the RMI server exports the `JMXConnectorServer` it acts as an RMI _client_ towards the RMI registry, and as such you must specify the TLS system properties as detailed below.
+====
+
+You must edit the `$JETTY_BASE/start.d/jmx-remote-ssl.ini` file and add the TrustStore path and password:
+
+.$JETTY_BASE/start.d/jmx-remote-ssl.ini
+----
+--module=jmx-remote-ssl
+
+# System properties necessary for non-trusted certificates.
+-Djavax.net.ssl.trustStore=/path/to/trustStore.p12
+-Djavax.net.ssl.trustStorePassword=password
+----
+
+[IMPORTANT]
+====
+The TrustStore must contain the certificate you want to trust.
+
+If you are using self-signed certificates, the KeyStore already contains the self-signed certificate and therefore the KeyStore can be used as a TrustStore, and the system properties above can refer to the KeyStore path and password.
+====
+
+JMX compliant tools that offer a graphical user interface also must be started specifying the TrustStore path and password.
+
+For example, to launch https://adoptium.net/jmc.html[Java Mission Control (JMC)]:
+
+----
+$ jmc -vmargs -Djavax.net.ssl.trustStore=/path/to/trustStore.p12 -Djavax.net.ssl.trustStorePassword=password
+----
diff --git a/documentation/jetty/modules/operations-guide/pages/jndi/index.adoc b/documentation/jetty/modules/operations-guide/pages/jndi/index.adoc
new file mode 100644
index 00000000000..03bdf898269
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/jndi/index.adoc
@@ -0,0 +1,391 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= JNDI
+
+Enable the `plus` module in order to be able to use JNDI resources in your webapp.
+If you already have the `annotations` module enabled, then it will already be enabled.
+
+If you have extra jars associated with your JNDI resources, eg database drivers etc, that are not located inside your webapp then you should place those jars into your `$jetty.base/lib/ext` directory.
+If your base doesn't already contain this directory, then enable the `ext` module, and Jetty will create the directory for you and ensure its contents are on the server's classpath.
+
+You can now declare JNDI resources and reference them within your webapps.
+
+== Declaring resources
+
+You must declare the objects you want bound into the environment so that you can then hook them into your webapp via `env-entry`, `resource-ref` and `resource-env-refs` in `web.xml`, `web-fragment.xml` or `override-web.xml`.
+
+You make these declarations in Jetty XML files that are either _external_ or _internal_ to your webapp.
+A server or context XML file is external to your webapp.
+The special `WEB-INF/jetty-env.xml` file is internal to your webapp.
+See the section on <> for more information on how to choose in which XML file to place your declarations.
+
+For now, let's look at _what_ you declare in the XML file, regardless of its location.
+
+Declaring a JDNI resource to be referenced later in your webapp is accomplished by declaring new instances of the following types:
+
+<>::
+Used for `env-entry` type of entries
+<>::
+Used for most other type of resources
+<>::
+For a JTA manager
+< >::
+For the link between a `web.xml` resource name and a naming entry
+
+Declarations of each of these types follow a similar pattern:
+
+[,xml,subs=verbatim]
+----
+
+
+
+
+
+----
+<1> Defines a resource to Jetty.
+<2> Specifies the <> of the resource.
+<3> Specifies the name of the resource which will be looked up by the webapp relative to the `java:comp/env` namespace.
+<4> Specifies the value of the resource.
+
+
+[[env]]
+=== org.eclipse.jetty.plus.jndi.EnvEntry
+
+Sometimes it is useful to pass configuration information to a webapp at runtime that you either cannot or cannot conveniently code into a `web.xml` ``.
+In such cases, you can use the `org.eclipse.jetty.plus.jndi.EnvEntry` class, and optionally even override an entry of the same name in `web.xml`.
+
+Here's an example that defines the equivalent of an `env-entry` called `mySpecialValue` with value `4000` that overrides an `` declaration of the same name in web.xml:
+
+[,xml,subs=verbatim]
+----
+
+
+ mySpecialValue
+ 4000
+ true
+
+----
+<1> Define an `EnvEntry` that corresponds to an ``.
+<2> <> at the JVM level.
+<3> The name of the entry, corresponding to a lookup by the webapp of `java:comp/env/mySpecialValue`.
+<4> The value of the entry, in this case the integer value `4000`.
+<5> `true` means to override the value of an `` of the same name in `web.xml`.
+
+Note that if you don't want to override the `web.xml` value, simply omit the last argument, or set it to `false`.
+
+The Servlet Specification allows binding only the following object types to an `env-entry`:
+
+* java.lang.String
+* java.lang.Integer
+* java.lang.Float
+* java.lang.Double
+* java.lang.Long
+* java.lang.Short
+* java.lang.Character
+* java.lang.Byte
+* java.lang.Boolean
+
+Jetty is a little more flexible and allows you to also bind:
+
+* custom POJOs
+* http://docs.oracle.com/javase/1.5.0/docs/api/javax/naming/Reference.html[`javax.naming.References`]
+* http://docs.oracle.com/javase/1.5.0/docs/api/javax/naming/Referenceable.html[`javax.naming.Referenceables`]
+
+Be aware that if you take advantage of this feature, your web application is __not portable__.
+
+[[resource]]
+=== org.eclipse.jetty.plus.jndi.Resource
+
+You can configure any type of resource that you want to refer to in `web.xml` via a `resource-ref` or `resource-env-ref` by using the `org.eclipse.jetty.plus.jndi.Resource` type of naming entry.
+
+You provide the scope, the name of the object (relative to `java:comp/env`) and a POJO, `javax.naming.Reference` or `javax.naming.Referenceable` instance.
+
+Let's examine how to configure some of the most common types of resources.
+
+==== DataSources
+
+In this example, we'll configure a http://db.apache.org/derby[Derby] DataSource named `jdbc/myds`:
+
+[,xml,subs=verbatim]
+----
+
+
+
+ jdbc/myds
+
+
+ test
+ create
+
+
+
+
+----
+
+This would linked into the webapps JNDI namespace via an entry in a `web.xml` like so:
+
+[,xml,subs=verbatim]
+----
+
+ jdbc/myds
+ javax.sql.DataSource
+ Container
+
+----
+
+[NOTE]
+====
+When configuring Resources, ensure that the type of object you configure matches the type of object you expect to look up in `java:comp/env`.
+For database connection factories, this means that the object you register as a Resource _must_ implement the `javax.sql.DataSource`Â interface.
+
+Also note that the http://jcp.org/aboutJava/communityprocess/pr/jsr244/index.html[J2EE Specification] recommends storing DataSources relative to `jdbc/` and thus looked up by the application as `java:comp/env/jdbc/xxx`.
+Eg The Datasource bound in Jetty as `jdbc/users` would be looked up by the application as `java:comp/env/jdbc/users`
+
+====
+
+//TODO For more examples of datasource configurations, see xref:jndi-datasource-examples[].
+
+
+==== JMS Queues, Topics and ConnectionFactories
+
+Jetty can bind any implementation of the JMS destinations and connection factories.
+
+Here is an example of binding an http://activemq.apache.org[ActiveMQ] in-JVM connection factory:
+
+[,xml,subs=verbatim]
+----
+
+
+
+ jms/connectionFactory
+
+
+ vm://localhost?broker.persistent=false
+
+
+
+
+----
+
+The corresponding entry in `web.xml` to bind the ConnectionFactory into the webapp's JNDI namespace would be:
+
+[,xml,subs=verbatim]
+----
+
+ jms/connectionFactory
+ javax.jms.ConnectionFactory
+ Container
+
+----
+
+[NOTE]
+====
+The http://jcp.org/aboutJava/communityprocess/pr/jsr244/index.html[J2EE Specification] recommends storing JMS connection factories under `jms/`.
+Eg The ConnectionFactory bound in Jetty as `jms/inqueue` would be looked up by the application as `java:comp/env/jms/inqueue`.
+====
+
+==== Mail
+
+To configure access to `javax.mail.Session` from within a webapp, declare an `org.eclipse.jetty.plus.jndi.Resource` with an `org.eclipse.jetty.jndi.factories.MailSessionReference` that will hold the mail configuration and create the instance of the `Session` when it is referenced:
+
+[,xml,subs=verbatim]
+----
+
+
+
+ mail/Session
+
+
+ fred
+ OBF:1xmk1w261z0f1w1c1xmq
+
+
+ XXX
+ me@me
+ true
+
+
+
+
+
+
+----
+<1> Use the `org.eclipse.jetty.jndi.factories.MailSessionReference` class to hold the configuration.
+<2> Set the username for the mail instance.
+<3> Set the password for the mail instance - use Jetty's secure password obfuscation to obscure the password.
+<4> Set all other applicable properties.
+
+The webapp performs a lookup for `java:comp/env/mail/Session` at runtime and obtains a `javax.mail.Session` that has the correct configuration to permit it to send email via SMTP.
+
+[NOTE]
+====
+Jetty does not provide the `javax.mail` and `javax.activation` jars.
+
+Note also that the http://jcp.org/aboutJava/communityprocess/pr/jsr244/index.html[J2EE Specification] recommends storing JavaMail connection factories under `mail/`.
+Eg The `MailSessionReference` bound to jetty as `mail/smtp` would be looked up by the application as `java:comp/env/mail/smtp`.
+====
+
+[[tx]]
+=== org.eclipse.jetty.plus.jndi.Transaction
+
+To perform distributed transactions with your resources, a _transaction manager_ that supports the JTA interfaces is required.
+The transaction manager is looked up by the application as `java:comp/UserTransaction`.
+
+Jetty does not ship with a JTA manager, but _does_ provide the infrastructure to plug in the JTA manager of your choice.
+
+Use the link:{javadoc-url}/org/eclipse/jetty/plus/jndi/Transaction.html[org.eclipse.jetty.plus.jndi.Transaction] object in a <> to configure the JTA manager.
+
+The following example configures the http://www.atomikos.com/[Atomikos] transaction manager:
+
+[,xml,subs=verbatim]
+----
+
+
+
+
+
+----
+
+Jetty will automatically bind this JTA manager to the webapp's JNDI namespace at `java:comp/UserTransaction`.
+
+[[link]]
+=== org.eclipse.jetty.plus.jndi.Link
+
+Usually, the name you provide for the `org.eclipse.jetty.plus.jndi.Resource` is the same name you reference in `web.xml`.
+This ensures that the two are linked together and thus accessible to your webapp.
+
+However, if the names cannot be the same, then it is possible to effectively alias one to another using an `org.eclipse.jetty.plus.jndi.Link`.
+
+Let's look at an example.
+
+Supposing you have a declaration for a Datasource named `jdbc/workforce` in a Jetty context XML file, but your web.xml wants to link to a `` named `jdbc/employees`, and you cannot edit the web.xml.
+You can create a `WEB-INF/jetty-env.xml` file with an `org.eclipse.jetty.plus.jndi.Link` that ties together the names `jdbc/workforce` and `jdbc/employees`:
+
+The context XML file declares `jdbc/workforce`:
+
+[,xml,subs=verbatim]
+----
+
+
+
+ jdbc/workforce
+
+
+ test
+ create
+
+
+
+
+----
+
+The `web.xml` refers to it as `jdbc/employees`:
+
+[,xml,subs=verbatim]
+----
+
+ jdbc/employees
+ javax.sql.DataSource
+ Container
+
+----
+
+Create a `WEB-INF/jetty-env.xml` file with a `org.eclipse.jetty.plus.jndi.Link` to link these names together:
+
+[,xml,subs=verbatim]
+----
+
+
+ jdbc/employees
+ jdbc/workforce
+
+----
+<1> The name as referenced in the `web.xml` file.
+<2> The name as referenced in the context XML file.
+
+[[xml]]
+=== Jetty XML files
+
+You can define naming resources in three places:
+
+Server XML file::
+Naming resources defined in a server XML file are <> at either the JVM level or the `org.eclipse.jetty.server.Server` level.
+The classes for the resource _must_ be visible at the Jetty *container* level.
+If instead the classes for the resource only exist inside your webapp, you must declare it in a `WEB-INF/jetty-env.xml` file.
+Context XML file::
+Entries in a context XML file should be <> at the level of the webapp to which they apply (although it is possible to use a less strict scoping level of Server or JVM, but not recommended).
+As with resources declared in a server XML file, classes associated with the resource _must_ be visible on the *container's* classpath.
+WEB-INF/jetty-env.xml::
+Naming resources in a `WEB-INF/jetty-env.xml` file are <> to the webapp in which the file resides.
+While you can enter JVM or Server scopes if you choose, we do not recommend doing so.
+The resources defined here may use classes from inside your webapp.
+
+[[scope]]
+=== Resource scoping
+
+Naming resources within Jetty belong to one of three different scopes, in increasing order of restrictiveness:
+
+*JVM scope:*
+The name is unique across the JVM instance, and is visible to all application code.
+This scope is represented by a `null` first parameter to the resource declaration.
+For example:
+[,xml,subs=verbatim]
+----
+
+
+ jms/connectionFactory
+
+
+ vm://localhost?broker.persistent=false
+
+
+
+----
+<1> Empty first arg equates to JVM scope for the object bound to name `jms/connectionFactory`.
+
+*Server scope:*
+The name is unique to a Server instance, and is only visible to applications associated with that instance.
+This scope is represented by referencing the Server instance as the first parameter to the resource declaration.
+For example:
+[,xml,subs=verbatim]
+----
+
+
+ jms/connectionFactory
+
+
+ vm://localhost?broker.persistent=false
+
+
+
+----
+<1> We refer to the id `Server` which identifies the default `org.eclipse.jetty.server.Server` instance.
+
+*Webapp scope:*
+The name is unique to the `org.eclipse.jetty.webapp.WebAppContext` instance, and is only visible to that application.
+This scope is represented by referencing the instance as the first parameter to the resource declaration.
+For example:
+[,xml,subs=verbatim]
+----
+
+
+ jms/connectionFactory
+
+
+ vm://localhost?broker.persistent=false
+
+
+
+----
+<1> We refer to an instance of an `org.eclipse.jetty.webapp.WebAppContext` which has been previously defined.
diff --git a/documentation/jetty/modules/operations-guide/pages/jsf-taglibs/index.adoc b/documentation/jetty/modules/operations-guide/pages/jsf-taglibs/index.adoc
new file mode 100644
index 00000000000..32123aea68b
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/jsf-taglibs/index.adoc
@@ -0,0 +1,41 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= JavaServer Faces TagLibs
+
+If you want to use JSF with your webapp, you should copy the relevant jars from your implementation of choice into your `$JETTY_BASE` directory, ideally into `$JETTY_BASE/lib/ext`.
+If that directory does not exist, enable the `ext` module, which will create the directory and ensure all jars within it are put onto the container classpath.
+
+
+Then you will need to tell Jetty which of those jars contains the `+*.tld+` files.
+To accomplish that, you need to specify either the name of the file or a pattern that matches the name/s of the file/s as the `org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern` context attribute.
+You will need to preserve the existing value of the attribute, and add in your extra pattern.
+
+Here's an example of using a context xml file to add in a pattern to match files starting with `jsf-`, which contain the `+*.tld+` files:
+
+[,xml,subs=verbatim]
+----
+
+
+
+
+
+ org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern
+ .*/jetty-servlet-api-[^/]*\.jar$|.*/javax.servlet.jsp.jstl-.*\.jar$|.*/org.apache.taglibs.taglibs-standard-impl-.*\.jar$|.*/jsf-[^/]*\.jar$
+
+
+----
+<1> Configures a link:{javadoc-url}/org/eclipse/jetty/webapp/WebAppContext.html[`WebAppContext`], which is the Jetty component that represents a standard Servlet web application.
+<2> Specifies a context attribute.
+<3> Specifies the name of the context attribute.
+<4> Adds the additional pattern `+.*/jsf-[^/]*\.jar$+` to those already existing.
diff --git a/documentation/jetty/modules/operations-guide/pages/jsp/index.adoc b/documentation/jetty/modules/operations-guide/pages/jsp/index.adoc
new file mode 100644
index 00000000000..82bfdb74918
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/jsp/index.adoc
@@ -0,0 +1,170 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= Java Server Pages
+
+Jetty supports JSP via the `jsp` module, which is based on Apache Jasper:
+
+----
+include::{jetty-home}/modules/jsp.mod[]
+----
+
+Logging has been bridged to Jetty logging, so you can enable logging for the `org.apache.jasper` package, subpackages and classes as usual.
+
+== Configuration of the JSP Servlet
+
+The `org.eclipse.jetty.jsp.JettyJspServlet` is the servlet responsible for serving JSPs.
+
+It is configured as the default jsp servlet in the `$JETTY_HOME/etc/webdefault.xml` file.
+Notice that Jetty identifies the jsp servlet by the presence of the `id="jsp"` attribute in the `` declaration.
+
+That file maps the `org.eclipse.jetty.jsp.JettyJspServlet` to the following partial urls:
+
+* `+*.jsp+`
+* `+*.jspf+`
+* `+*.jspx+`
+* `+*.xsp+`
+* `+*.JSP+`
+* `+*.JSPF+`
+* `+*.JSPX+`
+* `+*.XSP+`
+
+You can change to a different servlet, change or add ````s or add extra ````s in your `web.xml` file.
+
+Here's an example of adding an `` to augment the definitions from the standard `webdefault.xml` file:
+
+[,xml,subs=verbatim]
+----
+
+ jsp
+
+ keepgenerated
+ true
+
+
+----
+<1> This identifies this servlet as the jsp servlet to Jetty.
+<2> This identifies this declaration as augmenting the already-defined servlet called `jsp`.
+<3> This init param controls whether the jsp servlet retains the `+*.java+` files generated during jsp compilation.
+<4> This sets the value of the init param
+
+Another element you might consider adding to the default setup is `async-supported`:
+
+[,xml,subs=verbatim]
+----
+
+ jsp
+ true
+
+----
+<1> This identifies this servlet as the jsp servlet to Jetty.
+<2> This identifies this declaration as augmenting the already-defined servlet called `jsp`.
+<3> By default, the jsp servlet does not support async.
+
+There are many configuration parameters for the Apache Jasper JSP Servlet, here are some of them:
+
+.JSP Servlet Parameters
+[cols=",,,",options="header"]
+|===
+|init param |Description |Default |`webdefault.xml`
+
+|checkInterval |If non-zero and `development` is `false`, background jsp recompilation is enabled. This value is the interval in seconds between background recompile checks.
+ |0 |–
+|classpath |The classpath is dynamically generated if the context has a URL classloader. The `org.apache.catalina.jsp_classpath`
+context attribute is used to add to the classpath, but if this is not set, this `classpath` configuration item is added to the classpath instead.` |- |–
+
+|classdebuginfo |Include debugging info in class file. |true |–
+
+|compilerClassName |If not set, defaults to the Eclipse jdt compiler. |- |–
+
+|compiler |Used if the Eclipse jdt compiler cannot be found on the
+classpath. It is the classname of a compiler that Ant should invoke. |–
+|–
+
+|compilerTargetVM |Target vm to compile for. |1.8 |1.8
+
+|compilerSourceVM |Sets source compliance level for the jdt compiler.
+|1.8 |1.8
+
+|development |If `true` recompilation checks occur at the frequency governed by `modificationTestInterval`. |true |–
+
+|displaySourceFragment |Should a source fragment be included in
+exception messages |true |–
+
+|dumpSmap |Dump SMAP JSR45 info to a file. |false |–
+
+|enablePooling |Determines whether tag handler pooling is enabled. |true |–
+
+|engineOptionsClass |Allows specifying the Options class used to
+configure Jasper. If not present, the default EmbeddedServletOptions
+will be used. |- |–
+
+|errorOnUseBeanInvalidClassAttribute |Should Jasper issue an error when
+the value of the class attribute in an useBean action is not a valid
+bean class |true |–
+
+|fork |Only relevant if you use Ant to compile JSPs: by default Jetty will use the Eclipse jdt compiler.|true |-
+
+|genStrAsCharArray |Option for generating Strings as char arrays. |false |–
+
+|ieClassId |The class-id value to be sent to Internet Explorer when
+using tags. |clsid:8AD9C840-044E-11D1-B3E9-00805F499D93 |–
+
+|javaEncoding |Pass through the encoding to use for the compilation.
+|UTF8 |–
+
+|jspIdleTimeout |The amount of time in seconds a JSP can be idle before
+it is unloaded. A value of zero or less indicates never unload. |-1 |–
+
+|keepgenerated |Do you want to keep the generated Java files around?
+|true |–
+
+|mappedFile |Support for mapped Files. Generates a servlet that has a
+print statement per line of the JSP file |true |–
+
+|maxLoadedJsps |The maximum number of JSPs that will be loaded for a web
+application. If more than this number of JSPs are loaded, the least
+recently used JSPs will be unloaded so that the number of JSPs loaded at
+any one time does not exceed this limit. A value of zero or less
+indicates no limit. |-1 |–
+
+|modificationTestInterval |If `development=true`, interval between
+recompilation checks, triggered by a request. |4 |–
+
+|quoteAttributeEL | When EL is used in an attribute value on a JSP page, should the rules for quoting of attributes described in JSP.1.6 be applied to the expression
+ |true |-
+
+|recompileOnFail |If a JSP compilation fails should the
+modificationTestInterval be ignored and the next access trigger a
+re-compilation attempt? Used in development mode only and is disabled by
+default as compilation may be expensive and could lead to excessive
+resource usage. |false |–
+
+|scratchDir |Directory where servlets are generated. The default is the value of the context attribute `javax.servlet.context.tempdir`, or the system property `java.io.tmpdir` if the context attribute is not set. |– |–
+
+|strictQuoteEscaping |Should the quote escaping required by section JSP.1.6 of the JSP specification be applied to scriplet expression.
+ |true|-
+
+|suppressSmap |Generation of SMAP info for JSR45 debugging. |false |–
+
+|trimSpaces |Should template text that consists entirely of whitespace be removed from the output (true), replaced with a single space (single) or left unchanged (false)? Note that if a JSP page or tag file specifies a trimDirectiveWhitespaces value of true, that will take precedence over this configuration setting for that page/tag.
+trimmed? |false |–
+
+|xpoweredBy |Generate an X-Powered-By response header. |false |false
+
+|===
+
+[NOTE]
+====
+If the value you set doesn't take effect, try using all lower case instead of camel case, or capitalizing only some of the words in the name, as Jasper is inconsistent in its parameter naming strategy.
+====
diff --git a/documentation/jetty/modules/operations-guide/pages/jstl/index.adoc b/documentation/jetty/modules/operations-guide/pages/jstl/index.adoc
new file mode 100644
index 00000000000..0bd9e57ace8
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/jstl/index.adoc
@@ -0,0 +1,22 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= JavaServer Pages Standard Tag Libraries
+
+The JavaServer Pages Standard Tag Library (JSTL) is part of the Jetty distribution, and is available via the `jstl` module:
+
+----
+include::{jetty-home}/modules/jstl.mod[]
+----
+
+When enabled, Jetty will make the JSTL tags available for your webapps.
diff --git a/documentation/jetty/modules/operations-guide/pages/keystore/index.adoc b/documentation/jetty/modules/operations-guide/pages/keystore/index.adoc
new file mode 100644
index 00000000000..bf6faecf1fb
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/keystore/index.adoc
@@ -0,0 +1,274 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= Configuring SSL/TLS KeyStores
+
+A KeyStore is a file on the file system that contains a private key and a public certificate, along with the certificate chain of the certificate authorities that issued the certificate.
+The private key, the public certificate and the certificate chain, but more generally the items present in a KeyStore, are typically referred to as "cryptographic material".
+
+Keystores may encode the cryptographic material with different encodings, the most common being https://en.wikipedia.org/wiki/PKCS_12[PKCS12], and are typically protected by a password.
+
+Refer to the xref:protocols/index.adoc#ssl[secure protocols section] for more information about how to configure a secure connector using a KeyStore.
+
+[[create]]
+== Creating a KeyStore
+
+KeyStores are created with the JDK tool `$JAVA_HOME/bin/keytool`.
+
+The following command creates a KeyStore file containing a private key and a self-signed certificate:
+
+[source,subs=verbatim]
+----
+keytool
+ -genkeypair <1>
+ -alias mykey <2>
+ -validity 90 <3>
+ -keyalg RSA <4>
+ -keysize 2048 <5>
+ -keystore /path/to/keystore.p12 <6>
+ -storetype pkcs12 <7>
+ -dname "CN=domain.com, OU=Unit, O=Company, L=City, S=State, C=Country" <8>
+ -ext san=dns:www.domain.com,dns:domain.org <9>
+ -v <10>
+----
+<1> the command to generate a key and certificate pair
+<2> the alias name of the key and certificate pair
+<3> specifies the number of days after which the certificate expires
+<4> the algorithm _must_ be RSA (the DSA algorithm does not work for web sites)
+<5> indicates the strength of the key
+<6> the KeyStore file
+<7> the KeyStore type, stick with the standard PKCS12
+<8> the distinguished name (more below) -- customize it with your values for CN, OU, O, L, S and C
+<9> the extension with the subject alternative names (more below)
+<10> verbose output
+
+The command prompts for the KeyStore password that you must choose to protect the access to the KeyStore.
+
+[IMPORTANT]
+====
+The important parts of the command above are the _Common Name_ (CN) part of the distinguished name, and the subject alternative names (SAN).
+
+The CN value must be the main domain you own and that you want to use for your web applications.
+For example, if you have bought domains `domain.com` and `domain.org`, you want to specify `CN=domain.com` as your main domain.
+
+Furthermore, to specify additional domains or subdomains within the same certificate, you must specify the SAN extension.
+In the example above, `san=dns:www.domain.com,dns:domain.org` specifies `www.domain.com` and `domain.org` as alternative names for your web applications (that you can configure using xref:deploy/index.adoc#virtual-hosts[virtual hosts]).
+
+In rare cases, you may want to specify IP addresses, rather than domains, in the SAN extension.
+The syntax in such case is `san=ip:127.0.0.1,ip:[::1]`, which specifies as subject alternative names IPv4 `127.0.0.1` and IPv6 `[::1]`.
+====
+
+[[create-many]]
+=== KeyStores with Multiple Entries
+
+A single KeyStore may contain multiple key/certificate pairs.
+This is useful when you need to support multiple domains on the same Jetty server (typically accomplished using xref:deploy/index.adoc#virtual-hosts[virtual hosts]).
+
+You can create multiple key/certificate pairs as detailed in the <>, provided that you assign each one to a different alias.
+
+Compliant TLS clients will send the xref:protocols/index.adoc#ssl-sni[TLS SNI extension] when creating new connections, and Jetty will automatically choose the right certificate by matching the SNI name sent by the client with the CN or SAN of certificates present in the KeyStore.
+
+[[csr]]
+== Creating a Certificate Signing Request
+
+Self-signed certificates are not trusted by browsers and generic clients: you need to establish a trust chain by having your self-signed certificate signed by a certificate authority (CA).
+
+Browsers and generic clients (e.g. Java clients) have an internal list of trusted certificate authorities root certificates; they use these trusted certificates to verify the certificate they received from the server when they connect to your web applications.
+
+To have your self-signed certificate signed by a certificate authority you first need to produce a _certificate signing request_ (CSR):
+
+[source,subs=verbatim]
+----
+keytool
+ -certreq <1>
+ -file domain.com.csr <2>
+ -keystore keystore.p12 <3>
+----
+<1> the command to generate a certificate signing request
+<2> the file to save the CSR
+<3> the keystore that contains the self-signed certificate
+
+Then, you have to send the CSR file to the certificate authority of your choice, and wait for their reply (they will probably require a proof that you really own the domains indicated in your certificate).
+
+Eventually, the certificate authority will reply to you with one or more files containing the CA certificate chain, and your certificate signed by their certificate chain.
+
+[[csr-import]]
+== Importing the Signed Certificate
+
+The file you receive from the CA is typically in PEM format, and you *must* import it back into the same KeyStore file you used to generate the CSR.
+You must import *both* the certificate chain and your signed certificate.
+
+First, import the certificate chain:
+
+[source,subs=verbatim]
+----
+keytool
+ -importcert <1>
+ -alias ca <2>
+ -file chain_from_ca.pem <3>
+ -keystore keystore.p12 <4>
+ -trustcacerts <5>
+ -v <6>
+----
+<1> the command to import certificates
+<2> use the `ca` alias to differentiate from the alias of the server certificate
+<3> the file containing the certificate chain received from the CA
+<4> your KeyStore file
+<5> specify that you trust CA certificates
+<6> verbose output
+
+Then, import the signed certificate:
+
+----
+keytool
+ -importcert
+ -file signed_certificate.pem
+ -keystore keystore.p12
+ -trustcacerts
+ -v
+----
+
+Now you have a trusted certificate in your KeyStore that you can use for the domains of your web applications.
+
+// TODO: add a section about renewal?
+
+Refer to the section about configuring xref:protocols/index.adoc#ssl[secure protocols] to configure the secure connector with your newly created KeyStore.
+
+[[client-authn]]
+== Creating a KeyStore for Client Certificate Authentication
+
+For the majority of secure web sites, it is the client (typically the browser) that validates the certificate sent by the server (by verifying the certificate chain).
+This is the _server domain certificate_.
+
+However, the TLS protocol supports a _mutual authentication_ mode where also the client must send a certificate to the server, that the server validates.
+
+You typically want to sign the client certificate(s) with a server certificate that you control, and you must distribute the client certificate(s) to all the clients that need it, and redistribute the client certificates when they expire.
+The _server authentication certificate_ may be different from the _server domain certificate_, but it's typically stored in the same KeyStore for simplicity (although under a different alias).
+
+First, you want to create the private key and server authentication certificate that you will use to sign client certificates:
+
+[source,subs=verbatim]
+----
+keytool
+ -genkeypair
+ -alias server_authn <1>
+ -validity 90
+ -keyalg RSA
+ -keysize 2048
+ -keystore keystore.p12 <2>
+ -storetype pkcs12
+ -dname "CN=server_authn, OU=Unit, O=Company, L=City, S=State, C=Country" <3>
+ -ext bc=ca:true <4>
+ -v
+----
+<1> use the `server_authn` alias to differentiate from the alias of the server certificate
+<2> the KeyStore file
+<3> the CN is not that important, since this certificate will not be validated by clients
+<4> the extension with the basic constraints (more below)
+
+IMPORTANT: The important difference with the <> is the _basic constraints_ extension (`bc`) that indicates that this certificates acts as a certificate authority (`ca:true`).
+
+Now you want to export both the private key and server authentication certificate.
+Unfortunately, the `keytool` program cannot export private keys, so you need to use a different command line program like `openssl`, or a graphical program like https://keystore-explorer.org/[KeyStore Explorer].
+
+Let's use `openssl` to export the server authentication private key:
+
+----
+openssl
+ pkcs12
+ -in keystore.p12
+ -nodes
+ -nocerts
+ -out server_authn.key
+----
+
+Now let's export the server authentication certificate:
+
+----
+keytool
+ -exportcert
+ -keystore keystore.p12
+ -rfc
+ -file server_authn.crt
+ -v
+----
+
+At this point, you want to create a client KeyStore, so that you can sign the client certificate with the server authentication cryptographic material:
+
+[source,subs=verbatim]
+----
+keytool
+ -genkeypair
+ -validity 90
+ -keyalg RSA
+ -keysize 2048
+ -keystore client_keystore.p12 <1>
+ -storetype pkcs12
+ -dname "CN=client, OU=Unit, O=Company, L=City, S=State, C=Country" <2>
+ -v
+----
+<1> the client KeyStore file
+<2> the CN is not that important, since it will not be validated by the server
+
+Now produce a certificate signing request (CSR):
+
+----
+keytool
+ -certreq
+ -file client.csr
+ -keystore client_keystore.p12
+----
+
+Now you need to sign the CSR, but again the `keytool` program does not have this functionality, and you must resort again to use `openssl`:
+
+----
+openssl
+ x509
+ -req
+ -days 90
+ -in client.csr
+ -CA server_authn.crt
+ -CAkey server_authn.key
+ -CAcreateserial
+ -sha256
+ -out signed.crt
+----
+
+Now you need to import the server authentication certificate and the signed client certificate into the client KeyStore.
+
+First, the server authentication certificate:
+
+----
+keytool
+ -importcert
+ -alias ca
+ -file server_authn.crt
+ -keystore client_keystore.p12
+ -v
+----
+
+Then, the signed client certificate:
+
+----
+keytool
+ -importcert
+ -file signed.crt
+ -keystore client_keystore.p12
+ -v
+----
+
+Now you can distribute `client_keystore.p12` to your client(s).
+
+// TODO: add a section about renewal?
+
+Refer to the section about configuring xref:protocols/index.adoc#ssl[secure protocols] to configure the secure connector to require client authentication.
diff --git a/documentation/jetty/modules/operations-guide/pages/modules/custom.adoc b/documentation/jetty/modules/operations-guide/pages/modules/custom.adoc
new file mode 100644
index 00000000000..2b32fdfa51a
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/modules/custom.adoc
@@ -0,0 +1,235 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= Custom Jetty Modules
+
+In addition to the modules that come packaged with Jetty, you can create your own custom modules.
+
+NOTE: Make sure you have read the xref:modules/index.adoc[Jetty modules section] if you are not familiar with the concepts used in this section.
+
+Custom modules can be used for a number of reasons -- they can extend Jetty features, or add new features, or make additional libraries available to the server, etc.
+
+[[modify]]
+== Modifying an Existing Module
+
+The standard Jetty modules typically come with a number of configurable properties that can be easily customized without the need of writing a custom module.
+
+However, there may be cases where the customization is more complex than a simple property, and a custom module is necessary.
+
+For example, let's assume that you want to modify the order of the TLS cipher suites offered by the server when a client connects, using the https://www.openssl.org/docs/man1.1.0/man1/ciphers.html[OpenSSL cipher list format].
+
+The Jetty class that handles the TLS configuration is `SslContextFactory`, and it already has a method `setCipherComparator(Comparator)`; however, you need to pass your custom implementation, which cannot be represented with a simple module property.
+
+The `SslContextFactory` component is already allocated by the standard Jetty module `ssl`, so what you need to do is the following:
+
+* Write the custom cipher `Comparator` and package it into a `+*.jar+` file (exercise left to reader).
+* Write a custom Jetty XML file that calls the `SslContextFactory.setCipherComparator(Comparator)` method.
+* Write a custom Jetty module file that depends on the standard `ssl` module.
+
+Start with the custom Jetty XML file, `$JETTY_BASE/etc/custom-ssl.xml`:
+
+.custom-ssl.xml
+[,xml]
+----
+
+
+
+ [
+ ]
+
+
+
+ ECDH+AESGCM:ECDH+AES256:!aNULL:!MD5:!DSS:!ADH
+
+
+
+
+
+
+----
+<1> Reference the existing `SslContextFactory` object created by the standard `ssl` module using its `id`.
+<2> Call the `setCipherComparator()` method.
+<3> Instantiate your custom cipher comparator.
+<4> Pass to the constructor the ordering string in OpenSSL format, reading it from the module property `com.acme.ssl.cipherList`.
+
+CAUTION: The cipher list used above may not be secure -- it's just an example.
+
+Then write your custom module in the `$JETTY_BASE/modules/custom-ssl.mod` file:
+
+.custom-ssl.mod
+[source,subs=verbatim]
+----
+[description]
+Customizes the standard ssl module.
+
+[tags] <1>
+acme
+
+[depends] <2>
+ssl
+
+[lib] <3>
+lib/custom-cipher-comparator.jar
+
+[xml] <4>
+etc/custom-ssl.xml
+
+[ini-template] <5>
+## The cipher list in OpenSSL format.
+# com.acme.ssl.cipherList=ECDH+AESGCM:ECDH+AES256:!aNULL:!MD5:!DSS:!ADH
+
+----
+<1> A tag that characterizes this custom module (see xref:modules/index.adoc#directive-tags[here]).
+<2> This custom module depends on the standard `ssl` module.
+<3> The custom cipher comparator class is compiled and packaged into this `+*.jar+` file.
+<4> The custom Jetty XML file from above.
+<5> The text that will be copied in the `custom-ssl.ini` file when this custom module will be enabled.
+
+Now you can xref:start/index.adoc#configure-enable[enable] the custom module with the following command issued from the `$JETTY_BASE` directory:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=https,custom-ssl
+----
+
+The command above will produce the following `$JETTY_BASE` directory structure:
+
+[source,subs=normal]
+----
+$JETTY_BASE
+├── etc
+│ └── custom-ssl.xml
+├── modules
+│ └── custom-ssl.mod
+├── resources
+│ └── jetty-logging.properties
+└── start.d
+ ├── https.ini
+ └── ##custom-ssl.ini##
+----
+
+In the custom XML file you have used a custom module property to parametrize your custom cipher comparator.
+This custom module property was then referenced in the `[ini-template]` section of the custom module file, so that when the custom module is enabled, a correspondent `custom-ssl.ini` file is created.
+
+In this way, updating the cipher list won't require you to update the XML file, but just the `custom-ssl.ini` file.
+
+[[create]]
+== Creating a New Module
+
+In the cases where you need to enhance Jetty with a custom functionality, you can write a new Jetty module that provides it.
+
+For example, let's assume that you need to add a custom auditing component that integrates with the auditing tools used by your company.
+This custom auditing component should measure the HTTP request processing times and record them (how they are recorded is irrelevant here -- could be in a local log file or sent via network to an external service).
+
+The Jetty libraries already provide a way to measure HTTP request processing times via xref:programming-guide:server/http.adoc#channel-events[`HttpChannel` events]: you write a custom component that implements the `HttpChannel.Listener` interface and add it as a bean to the server `Connector` that receives the HTTP requests.
+
+The steps to create a Jetty module are similar to those necessary to <>:
+
+* Write the auditing component and package it into a `+*.jar+` file.
+* Write a custom Jetty XML file that wires the auditing component to the `ServerConnector`.
+* Write a custom Jetty module file that puts everything together.
+
+Let's start with the auditing component, sketched below:
+
+[,java]
+----
+package com.acme.audit;
+
+public class AuditingHttpChannelListener implements HttpChannel.Listener {
+ // Auditing is implemented here.
+}
+----
+
+Let's assume that this class is compiled and packaged into `acme-audit.jar`, and that it has a dependency on `acme-util.jar`.
+Both `+*.jar+` files will be put in the `$JETTY_BASE/lib/` directory.
+
+Next, let's write the Jetty XML file that wires the auditing component to the `ServerConnector`, `$JETTY_BASE/etc/acme-audit.xml`:
+
+.acme-audit.xml
+[,xml,subs=verbatim,options=nowrap]
+----
+
+
+
+ [
+ ]
+
+
+
+
+
+
+
+
+
+
+
+----
+<1> Reference the existing clear-text HTTP `ServerConnector` object created by the standard `http` module.
+<2> Call `addBean()` on the `ServerConnector` to wire the auditing component.
+<3> Instantiate the auditing component.
+<4> Configure the auditing component with a property.
+
+The last step is to create the custom Jetty module file for the auditing component, `$JETTY_BASE/modules/acme-audit.mod`:
+
+.acme-audit.mod
+----
+[description]
+Adds auditing to the clear-text HTTP connector
+
+[tags] <1>
+acme
+audit
+
+[depends] <2>
+http
+
+[libs] <3>
+lib/acme-audit.jar
+lib/acme-util.jar
+
+[xml] <4>
+etc/acme-audit.xml
+
+[ini-template] <5>
+## An auditing property.
+# com.acme.audit.some.property=42
+----
+<1> The tags that characterize this custom module (see xref:modules/index.adoc#directive-tags[here]).
+<2> This custom module depends on the standard `http` module.
+<3> The `+*.jar+` files that contains the custom auditing component, and its dependencies.
+<4> The custom Jetty XML file from above.
+<5> The text that will be copied in the `acme-audit.ini` file when this custom module will be enabled.
+
+Now you can xref:start/index.adoc#configure-enable[enable] the custom auditing module with the following command issued from the `$JETTY_BASE` directory:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=http,acme-audit
+----
+
+The command above will produce the following `$JETTY_BASE` directory structure:
+
+[source,subs=normal]
+----
+$JETTY_BASE
+├── etc
+│ └── acme-audit.xml
+├── modules
+│ └── acme-audit.mod
+├── resources
+│ └── jetty-logging.properties
+└── start.d
+ ├── http.ini
+ └── ##acme-audit.ini##
+----
+
+Enabling the custom auditing component will create the `$JETTY_BASE/start.d/acme-audit.ini` module configuration file that you can edit to configure auditing properties.
diff --git a/documentation/jetty/modules/operations-guide/pages/modules/index.adoc b/documentation/jetty/modules/operations-guide/pages/modules/index.adoc
new file mode 100644
index 00000000000..be56b270f5a
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/modules/index.adoc
@@ -0,0 +1,493 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= Jetty Modules
+
+A Jetty _module_ provides one or more Java components that work together to implement one or more features.
+Such features could be listening for clear-text HTTP/1.1 requests, exposing Jetty components to JMX, provide hot-deployment of web applications, etc.
+
+Every Jetty feature is provided by a Jetty module.
+
+A Jetty module is defined in a `.mod` file, where `` is the module name (see also the <>).
+
+Jetty module files are read from the typical xref:start/index.adoc#configure[configuration source directories], under the `modules/` subdirectory; from higher priority to lower priority:
+
+* The `$JETTY_BASE/modules/` directory.
+* If a directory is specified with the `--include-jetty-dir` option, its `modules/` subdirectory.
+* The `$JETTY_HOME/modules/` directory.
+
+The standard Jetty modules that Jetty provides out-of-the-box are under `$JETTY_HOME/modules/`.
+
+xref:modules/custom.adoc[Custom Jetty modules] should be put under `$JETTY_BASE/modules/`.
+
+[[names]]
+== Module Names
+
+A Jetty module has a unique name.
+The module name is by default derived from the file name, so module file `acme.mod` identifies a module named `acme`.
+
+However, a module file may specify a <>+] directive for a _virtual_ module, so that many modules may provide a different implementation for the same feature.
+
+For example, among the standard modules provided by Jetty, the `server` module depends on the `logging` module, but there is no correspondent `logging.mod` file.
+
+However, the `logging-jetty.mod` file has, among others, this section:
+
+.logging-jetty.mod
+----
+[provides]
+logging|default
+----
+
+This section means that the `logging-jetty.mod` file provides the virtual module `logging`, and it is the default provider.
+
+The `logging-log4j2.mod` file has a similar section:
+
+.logging-log4j2.mod
+----
+[provides]
+logging
+----
+
+If there are no enabled modules that provide the `logging` virtual module, either explicitly or transitively, then the default provider is used, in this case `logging-jetty.mod`.
+
+Otherwise, a module that provides the `logging` virtual module is explicitly or transitively enabled, and the default provider is not used.
+
+[[components]]
+== Module Components
+
+A Jetty module may provide one or more Java components that implement a feature.
+These Java components are nothing more than regular Java classes that are instantiated and configured via xref:xml/index.adoc[Jetty XML] files.
+
+The Jetty XML file of a Jetty module may instantiate and assemble together its own components, or reference existing components from other Jetty modules to enhance or reconfigure them.
+
+The Jetty module's XML files are read from the typical xref:start/index.adoc#configure[configuration source directories], under the `etc/` subdirectory; from higher priority to lower priority:
+
+* The `$JETTY_BASE/etc/` directory.
+* If a directory is specified with the `--include-jetty-dir` option, its `etc/` subdirectory.
+* The `$JETTY_HOME/etc/` directory.
+
+The standard Jetty modules XML files that Jetty provides out-of-the-box are under `$JETTY_HOME/etc/`.
+
+For example, a Jetty XML file that allocates Jetty's `QueuedThreadPool` could be as simple as:
+
+[,xml]
+.jetty-threadpool.xml
+----
+
+
+
+
+
+
+
+
+
+----
+
+Note how the Jetty XML file above is allocating (with the `` element) a `QueuedThreadPool` instance, giving it the unique `id` of `threadPool` (so that other modules can reference it, if they need to).
+It is then calling the setter method `QueuedThreadPool.setMaxThreads(int)` with the value defined by the <> `jetty.threadPool.maxThreads`; if the property value is not defined, it will have the default value of `256`.
+
+This is nothing more than Java code in XML format with configurable properties support that can be leveraged by the xref:start/index.adoc[Jetty start mechanism].
+
+The Jetty module's XML files make easy to instantiate and assemble Java components (just write the equivalent Java code in XML format), and make easy to configure them by declaring module properties that can be easily customized elsewhere (for example, in `+*.ini+` files as described in xref:start/index.adoc#configure-enable[this section], or on the command line as described in xref:start/index.adoc#start[this section]).
+
+[IMPORTANT]
+====
+Remember that the standard Jetty XML files in `$JETTY_HOME/etc/` should not be modified.
+
+Even if you need to modify a standard Jetty component, write a new Jetty XML file, save it under `$JETTY_BASE/etc/`, and create a xref:modules/custom.adoc[custom Jetty module] so that it gets processed when Jetty starts.
+====
+
+[[properties]]
+== Module Properties
+
+A Jetty module property is declared in the <> via the `` element.
+Modules properties are used to parametrize Jetty components so that you can customize their values when Jetty starts, rather than hard-coding it in the XML files.
+
+NOTE: You can declare your own properties, but the `+jetty.*+` namespace is reserved.
+
+A module property can be given a value in a Jetty module `[ini]` section (see <>), in a `+*.ini+` file as described in xref:start/index.adoc#configure-enable[this section], or on the command line as described in xref:start/index.adoc#start[this section].
+
+The syntax to specify a property value is the following:
+
+=::
+Sets the property value unconditionally.
++=::
+Appends the value to the existing value.
+This is useful to append a value to properties that accept a comma separated list of values, for example:
++
+----
+jetty.webapp.addServerClasses+=,com.acme
+----
++
+// TODO: check what happens if the property is empty and +=,value is done: is the comma stripped? If so add a sentence about this.
+?=::
+Sets the property value only if it is not already set.
+This is useful to define default values, for example for "version" properties, where the "version" property can be explicitly configured to a newer version, but if it is not explicitly configured it will have a default version (see also xref:start/index.adoc#configure-custom-module[here]).
+For example:
++
+----
+conscrypt.version?=2.5.1
+jetty.sslContext.provider?=Conscrypt
+----
+
+[[directives]]
+== Module Directives
+
+Lines that start with `#` are comments.
+
+[[directive-description]]
+=== [description]
+
+A text that describes the module.
+
+This text will be shown by the xref:start/index.adoc#configure[Jetty start mechanism] when using the `--list-modules` command.
+
+[[directive-tags]]
+=== [tags]
+
+A list of words that characterize the module.
+
+Modules that have the same tags will be shown by the Jetty start mechanism when using the `--list-modules=` command.
+
+.example.mod
+----
+[tags]
+demo
+webapp
+jsp
+----
+
+[[directive-provides]]
+=== [provides]
+
+A module name with an optional `default` specifier.
+
+As explained in the <>, there can be many module files each providing a different implementation for the same feature.
+
+The format is:
+
+----
+[provides]
+[|default]
+----
+
+where the `|default` part is optional and specifies that the module is the default provider.
+
+[[directive-depends]]
+=== [depends]
+
+A list of module names that this module depends on.
+
+For example, the standard module `http` depends on module `server`.
+Enabling the `http` module also enables, transitively, the `server` module, since the `http` module cannot work without the `server` module; when the `server` module is transitively enabled, the modules it depends on will be transitively enabled, and so on recursively.
+
+The `[depends]` directive establishes a https://en.wikipedia.org/wiki/Partially_ordered_set[_partial order_] relationship among modules so that enabled modules can be sorted and organized in a graph.
+Circular dependencies are not allowed.
+
+The order of the enabled modules is used to determine the processing of the configuration, for example the order of processing of the <>+] section, the order of processing of XML files defined in the <>+] section, etc.
+
+[[directive-after]]
+=== [after]
+
+This directive indicates that this module is ordered after the listed module names, if they are enabled.
+
+For example, module `https` is `[after]` module `http2`.
+Enabling the `https` module _does not_ enable the `http2` module.
+
+However, if the `http2` module is enabled (explicitly or transitively), then the `https` module is <> _after_ the `http2` module.
+In this way, you are guaranteed that the `https` module is processed after the `http2` module.
+
+[[directive-before]]
+=== [before]
+
+This directive indicates that this module is ordered before the listed module names, if they are enabled.
+
+One use of this directive is to create a prerequisite module without the need to modify the `depends` directive of an existing module.
+For example, to create a custom `org.eclipse.jetty.server.Server` subclass instance to be used by the standard `server` module, without modifying the existing `server.mod` file nor the `jetty.xml` file that it uses. This can be achieved by creating the `custom-server` xref:modules/custom.adoc[Jetty custom module]:
+
+.custom-server.mod
+----
+[description]
+This module creates a custom Server subclass instance.
+
+[before]
+server
+
+[xml]
+etc/custom-server.xml
+----
+
+The `custom-server.xml` file is the following:
+
+.custom-server.xml
+[,xml]
+----
+
+
+
+
+----
+
+The presence of the `[before]` directive in `custom-server.mod` causes the processing of the `custom-server.xml` file to happen before the processing of the standard `jetty.xml` file referenced by the standard `server.mod` Jetty module.
+
+Thus, the instance assigned to the `Server` identifier is your custom `com.acme.server.CustomJettyServer` instance from the `custom-server.xml` file; this instance is then used while processing the `jetty.xml` file.
+
+[[directive-files]]
+=== [files]
+
+A list of paths (directories and/or files) that are necessary for the module, created or resolved when the module is enabled.
+
+Each path may be of the following types:
+
+Path Name::
+A path name representing a file, or a directory if the path name ends with `/`, such as `webapps/`.
+The file or directory will be created relative to `$JETTY_BASE`, if not already present.
++
+For example:
++
+----
+[files]
+logs/
+----
+
+Maven Artifact::
+An URI representing a Maven artifact to be downloaded from Maven Central, if not already present.
+Property expansion is supported.
++
+The format is:
++
+----
+[files]
+maven:////[/]|
+----
++
+where `` is optional, and `` after the `|` is the path under `$JETTY_BASE` where the downloaded file should be saved.
++
+For example:
++
+[,options=nowrap]
+----
+[files]
+maven://org.postgresql/postgresql/${postgresql-version}|lib/postgresql-${postgresql-version}.jar
+----
+
+BaseHome::
+An URI representing a `$JETTY_HOME` resource to be copied in `$JETTY_BASE`, if not already present.
+URIs of this type are typically only used by standard Jetty modules; custom modules should not need to use it.
++
+The format is:
++
+----
+[files]
+basehome:|
+----
++
+For example:
++
+----
+[files]
+basehome:modules/demo.d/demo-moved-context.xml|webapps/demo-moved-context.xml
+----
+
+HTTP URL::
+
+An `http://` or `https://` URL to be downloaded, if not already present.
++
+The format is:
++
+----
+[files]
+|
+----
++
+For example:
++
+----
+[files]
+https://acme.com/favicon.ico|webapps/acme/favicon.ico
+----
+
+[[directive-libs]]
+=== [libs]
+
+A list of paths, relative to the xref:start/index.adoc#configure[configuration source directories], of `+*.jar+` library files and/or directories that are added to the server class-path (or module-path when xref:start/start-jpms.adoc[running in JPMS mode]).
+
+The `[libs]` section if often used in conjunction with the `[files]` section.
+
+For example:
+
+----
+[files]
+maven://org.postgresql/postgresql/${postgresql-version}|lib/postgresql-${postgresql-version}.jar
+
+[libs]
+lib/postgresql-${postgresql-version}.jar
+----
+
+The `postgresql-.jar` artifact is downloaded from Maven Central, if not already present, into the `$JETTY_BASE/lib/` directory when the module is enabled.
+
+When Jetty starts, the `$JETTY_BASE/lib/postgresql-.jar` will be in the server class-path (or module-path).
+
+[[directive-xml]]
+=== [xml]
+
+A list of paths, relative to the xref:start/index.adoc#configure[configuration source directories], of Jetty `+*.xml+` files that are passed as program arguments to be processed when Jetty starts (see the xref:start/index.adoc#start-xml[section about assembling Jetty components]).
+
+Jetty XML files are read from the typical xref:start/index.adoc#configure[configuration source directories], under the `etc/` subdirectory.
+Standard Jetty XML files are under `$JETTY_HOME/etc/`, while custom Jetty XML files are typically under `$JETTY_BASE/etc/`.
+
+For example:
+
+----
+[xml]
+etc/custom/components.xml
+----
+
+[[directive-ini]]
+=== [ini]
+
+A list of program arguments to pass to the command line when Jetty is started.
+
+The program arguments may include any command line option (see xref:start/index.adoc#reference[here] for the list of command line options), <> and/or <>.
+
+A property defined in the `[ini]` section is available in the `+*.mod+` module file for property expansion, for example:
+
+----
+[ini]
+postgresql-version?=42.2.18
+
+[lib]
+lib/postgresql-${postgresql-version}.jar
+----
+
+In the example above, the `[lib]` section contains `$\{postgresql-version}`, a reference to property `postgresql-version` whose value is defined in the `[ini]` section.
+The expression `${}` _expands_ the property replacing the expression with the property value.
+
+See also the xref:start/start-jpms.adoc[JPMS section] for additional examples about the `[ini]` section.
+
+[[directive-ini-template]]
+=== [ini-template]
+
+A list of properties to be copied in the `+*.ini+` file generated when xref:start/index.adoc#configure-enable[the module is enabled].
+
+The list of properties is derived from the <> that declare them.
+
+The properties are typically assigned their default value and commented out, so that it is evident which properties have been uncommented and customized with a non-default value.
+
+[[directive-exec]]
+=== [exec]
+
+A list of JVM command line options and/or system properties passed to a forked JVM.
+
+When the `[exec]` section is present, the JVM running the Jetty start mechanism will fork another JVM, passing the JVM command line options and system properties listed in the `[exec]` sections of the enabled modules.
+
+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:start/index.adoc#configure-custom-module-exec[this section].
+
+You can avoid that the Jetty start mechanism forks the second JVM, as explained in xref:start/index.adoc#configure-dry-run[this section].
+
+[[directive-jpms]]
+=== [jpms]
+
+A list of JVM command line options related to the Java Module System.
+
+This section is processed only when Jetty is xref:start/start-jpms.adoc[started in JPMS mode].
+
+The directives are:
+
+add-modules::
+Equivalent to the JVM option `--add-modules`.
+The format is:
++
+----
+[jpms]
+add-modules: (,)*
+----
++
+where `module` is a JPMS module name.
+
+patch-module::
+Equivalent to the JVM option `--patch-module`.
+The format is:
++
+----
+[jpms]
+patch-module: =(:)*
+----
+where `module` is a JPMS module name.
+
+add-opens::
+Equivalent to the JVM option `--add-opens`.
+The format is:
++
+----
+[jpms]
+add-opens: /=(,)*
+----
+where `module` and `target-module` are a JPMS module names.
+
+add-exports::
+Equivalent to the JVM option `--add-exports`.
+The format is:
++
+----
+[jpms]
+add-exports: /=(,)*
+----
+where `module` and `target-module` are a JPMS module names.
+
+add-reads::
+Equivalent to the JVM option `--add-exports`.
+The format is:
++
+----
+[jpms]
+add-reads: =(,)*
+----
+where `module` and `target-module` are a JPMS module names.
+
+[[directive-license]]
+=== [license]
+
+The license under which the module is released.
+
+A Jetty module may be released under a license that is different from Jetty's, or use libraries that require end-users to accept their licenses in order to be used.
+
+You can put the license text in the `[license]` section, and when the Jetty module is enabled the license text will be printed on the terminal, and the user prompted to accept the license.
+If the user does not accept the license, the module will not be enabled.
+
+For example:
+
+----
+[license]
+Acme Project is an open source project hosted on GitHub
+and released under the Apache 2.0 license.
+https://www.apache.org/licenses/LICENSE-2.0.txt
+----
+
+[[directive-version]]
+=== [version]
+
+The minimum Jetty version for which this module is valid.
+
+For example, a module may only be valid for Jetty 10 and later, but not for earlier Jetty versions (because it references components that have been introduced in Jetty 10).
+
+For example:
+
+----
+[version]
+10.0
+----
+
+A Jetty module with such a section will only work for Jetty 10.0.x or later.
diff --git a/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc b/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc
new file mode 100644
index 00000000000..ab1c25bdb6b
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/modules/standard.adoc
@@ -0,0 +1,564 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= Standard Modules
+
+[[alpn]]
+== Module `alpn`
+
+The `alpn` module enables support for the ALPN negotiation mechanism of the TLS protocol.
+
+You can configure the list of application protocols negotiated by the ALPN mechanism, as well as the default protocol to use if the ALPN negotiation fails (for example, the client does not support ALPN).
+
+The module properties are:
+
+----
+include::{jetty-home}/modules/alpn.mod[tags=documentation]
+----
+
+[[bytebufferpool]]
+== Module `bytebufferpool`
+
+The `bytebufferpool` module allows you to configure the server-wide `ByteBuffer` pool.
+Pooling ``ByteBuffer``s results in less memory usage and less pressure on the Garbage Collector.
+
+``ByteBuffer``s are pooled in _buckets_; each bucket as a capacity that is a multiple of a capacity factor that you can configure.
+For example, if a request for a `ByteBuffer` of capacity 2000 is requested, and the capacity factor is 1024, then the pool will allocate a buffer from the second bucket, of capacity 2048 (1024 * 2).
+
+Applications that need to sustain many concurrent requests -- or load spikes -- may require many buffers during peak load. These buffers will remain pooled once the system transitions to a lighter load (or becomes idle), and it may be undesirable to retain a lot of memory for an idle system.
+
+It is possible to configure the max heap memory and the max direct memory that the pool retains.
+Excess buffers will not be pooled and will be eventually garbage collected.
+
+The module file is `$JETTY_HOME/modules/bytebufferpool.mod`:
+
+----
+include::{jetty-home}/modules/bytebufferpool.mod[]
+----
+
+Among the configurable properties, the most relevant are:
+
+`jetty.byteBufferPool.maxHeapMemory`::
+This property allows you to cap the max heap memory retained by the pool.
+
+`jetty.byteBufferPool.maxDirectMemory`::
+This property allows you to cap the max direct memory retained by the pool.
+
+[[console-capture]]
+== Module `console-capture`
+
+The `console-capture` module captures `System.out` and `System.err` output and appends it to a rolling file.
+
+The file is rolled every day at the midnight of the configured timezone.
+Old, rolled files are kept for the number of days specified by the `jetty.console-capture.retainDays` property.
+
+The module properties are:
+
+----
+include::{jetty-home}/modules/console-capture.mod[tags=documentation]
+----
+
+[[deploy]]
+== Module `deploy`
+
+The `deploy` module provides the deployment feature through a `DeploymentManager` component that watches a directory for changes (see xref:deploy/index.adoc[how to deploy web applications] for more information).
+
+Files or directories added in this monitored directory cause the `DeploymentManager` to deploy them as web applications; updating files already existing in this monitored directory cause the `DeploymentManager` to re-deploy the correspondent web application; removing files in this monitored directory cause the `DeploymentManager` to undeploy the correspondent web application (see also xref:deploy/index.adoc#rules[here] for more information).
+
+The module file is `$JETTY_HOME/modules/deploy.mod`:
+
+----
+include::{jetty-home}/modules/deploy.mod[]
+----
+
+Among the configurable properties, the most relevant are:
+
+`jetty.deploy.monitoredDir`::
+The name of the monitored directory.
+`jetty.deploy.scanInterval`::
+The scan period in seconds, that is how frequently the `DeploymentManager` wakes up to scan the monitored directory for changes.
+Setting `jetty.deploy.scanInterval=0` disabled _hot_ deployment so that only static deployment will be possible (see also xref:deploy/index.adoc#hot-static[here] for more information).
+
+[[http]]
+== Module `http`
+
+The `http` module provides the clear-text connector and support for the clear-text HTTP/1.1 protocol, and depends on the <>.
+
+The module properties to configure the clear-text connector are:
+
+----
+include::{jetty-home}/modules/http.mod[tags=documentation]
+----
+
+Among the configurable properties, the most relevant are:
+
+`jetty.http.port`::
+The network port that Jetty listens to for clear-text HTTP/1.1 connections -- default `8080`.
+`jetty.http.idleTimeout`::
+The amount of time a connection can be idle (i.e. no bytes received and no bytes sent) until the server decides to close it to save resources -- default `30` seconds.
+`jetty.http.acceptors`::
+The number of threads that compete to accept connections -- default 1. Use -1 to let the accept heuristic decides the value; the current heuristic calculates a value based on the number of cores).
+Refer to <> for more information about acceptor threads.
+`jetty.http.selectors`::
+The number of NIO selectors (with an associated thread) that manage connections -- default -1 (i.e. a select heuristic decides the value; the current heuristic calculates a value based on the number of cores).
+
+[[http-acceptors]]
+=== Configuration of Acceptors
+
+Accepting connections from remote clients may be configured as a blocking operation, or a non-blocking operation.
+
+When accepting connections is configured as a blocking operation (the number of acceptors is greater than zero), a thread is blocked in the `accept()` call until a connection is accepted, and other acceptor threads (if any) are blocked on the lock acquired by the accepting thread just before the `accept()` call.
+
+When the accepting thread accepts a connection, it performs a little processing of the just accepted connection, before forwarding it to other components.
+
+During this little processing other connections may be established; if there is only one accepting thread, the newly established connections are waiting for the accepting thread to finish the processing of the previously accepted connection and call again `accept()`.
+
+Servers that manage a very high number of connections that may (naturally) come and go, or that handle inefficient protocols that open and close connections very frequently (such as HTTP/1.0) may benefit of an increased number of acceptor threads, so that when one acceptor thread processes a just accepted connection, another acceptor thread can immediately take over accepting connections.
+
+When accepting connections is configured as a non-blocking operation (the number of acceptors is zero), then the server socket is set in non-blocking mode and added to a NIO selector.
+In this way, no dedicated acceptor threads exist: the work of accepting connections is performed by the selector thread.
+
+[[http-selectors]]
+=== Configuration of Selectors
+
+Performing a NIO `select()` call is a blocking operation, where the selecting thread is blocked in the `select()` call until at least one connection is ready to be processed for an I/O operation.
+There are 4 I/O operations: ready to be accepted, ready to be connected, ready to be read and ready to be written.
+
+A single NIO selector can manage thousands of connections, with the assumption that not many of them will be ready at the same time.
+
+For a single NIO selector, the ratio between the average number of selected connections over the total number of connections for every `select()` call depends heavily on the protocol but also on the application.
+
+Multiplexed TCP protocols such as HTTP/2 tend to be busier than duplex protocols such as HTTP/1.1, leading to a higher ratio.
+
+REST applications that exchange many little JSON messages tend to be busier than file server applications, leading to a higher ratio.
+
+The higher the ratio, the higher the number of selectors you want to have, compatibly with the number of cores -- there is no point in having 64 selector threads on a single core hardware.
+
+[[http2]]
+== Module `http2`
+
+The `http2` module enables support for the secure HTTP/2 protocol.
+
+The module properties are:
+
+----
+include::{jetty-home}/modules/http2.mod[tags=documentation]
+----
+
+// tag::rate-control[]
+The `jetty.http2.rateControl.maxEventsPerSecond` property controls the number of "bad" or "unnecessary" frames that a client may send before the server closes the connection (with code https://tools.ietf.org/html/rfc7540#section-7[`ENHANCE_YOUR_CALM`]) to avoid a denial of service.
+
+For example, an attacker may send empty `SETTINGS` frames to a server in a tight loop.
+While the `SETTINGS` frames don't change the server configuration and each of them is somehow harmless, the server will be very busy processing them because they are sent by the attacker one after the other, causing a CPU spike and eventually a denial of service (as all CPUs will be busy processing empty `SETTINGS` frames).
+
+The same attack may be performed with `PRIORITY` frames, empty `DATA` frames, `PING` frames, etc.
+
+[[http2c]]
+== Module `http2c`
+
+The `http2c` module enables support for the clear-text HTTP/2 protocol.
+
+The module properties are:
+
+----
+include::{jetty-home}/modules/http2c.mod[tags=documentation]
+----
+
+The `jetty.http2.rateControl.maxEventsPerSecond` property controls the number of "bad" or "unnecessary" frames that a client may send before the server closes the connection (with code https://tools.ietf.org/html/rfc7540#section-7[`ENHANCE_YOUR_CALM`]) to avoid a denial of service.
+
+For example, an attacker may send empty `SETTINGS` frames to a server in a tight loop.
+While the `SETTINGS` frames don't change the server configuration and each of them is somehow harmless, the server will be very busy processing them because they are sent by the attacker one after the other, causing a CPU spike and eventually a denial of service (as all CPUs will be busy processing empty `SETTINGS` frames).
+
+The same attack may be performed with `PRIORITY` frames, empty `DATA` frames, `PING` frames, etc.
+
+[[http3]]
+== Module `http3`
+
+The `http3` module enables support for the HTTP/3 protocol.
+
+The module properties are:
+
+----
+include::{jetty-home}/modules/http3.mod[tags=documentation]
+----
+
+[[http-forwarded]]
+== Module `http-forwarded`
+
+The `http-forwarded` module provides support for processing the `Forwarded` HTTP header (defined in https://tools.ietf.org/html/rfc7239[RFC 7239]) and the now obsoleted `X-Forwarded-*` HTTP headers.
+
+The module properties are:
+
+----
+include::{jetty-home}/modules/http-forwarded.mod[tags=documentation]
+----
+
+[[https]]
+== Module `https`
+
+The `https` module provides the HTTP/1.1 protocol to the <>.
+
+The module file is `$JETTY_HOME/modules/https.mod`:
+
+----
+include::{jetty-home}/modules/https.mod[]
+----
+
+[[jmx-remote]]
+== Module `jmx-remote`
+
+The `jmx-remote` module provides remote access to JMX clients.
+
+The module properties to configure remote JMX connector are:
+
+----
+include::{jetty-home}/modules/jmx-remote.mod[tags=documentation]
+----
+
+The system property `java.rmi.server.hostname` is specified with the usual notation, prepending a `-D` in front of the system property name.
+
+The system property `java.rmi.server.hostname` is uncommented because it is necessary in the default configuration -- most systems do not have the local name resolution configured properly for remote access.
+
+As an example, in a Linux machine named `beryl`, the `/etc/hosts` file may contain these entries:
+
+----
+127.0.0.1 localhost
+127.0.1.1 beryl
+----
+
+If the system property `java.rmi.server.hostname` is not specified, the RMI implementation uses the host name `beryl` to figure out the IP address to store in the RMI stub, in this case `127.0.1.1`.
+However, we the RMI server is configured to bind to `localhost`, i.e. `127.0.0.1`.
+
+If the system property `java.rmi.server.hostname` is not specified, the RMI client will try to connect to `127.0.1.1` (because that's what in the RMI stub) and fail because nothing is listening on that address.
+
+[[requestlog]]
+== Module `requestlog`
+
+The `requestlog` module provides HTTP request/response logging in the standard https://en.wikipedia.org/wiki/Common_Log_Format[NCSA format], or in a custom format of your choice.
+
+The module properties are:
+
+----
+include::{jetty-home}/modules/requestlog.mod[tags=documentation]
+----
+
+The property `jetty.requestlog.formatString` can be customized using format codes.
+
+javadoc::code:partial$org/eclipse/jetty/server/CustomRequestLog.java[]
+
+[[server]]
+== Module `server`
+
+The `server` module provides generic server support, and configures generic HTTP properties that apply to all HTTP protocols, the scheduler properties and the server specific properties.
+
+The `server` module depends on the <>, the <> and the xref:server/index.adoc#logging[`logging` module].
+
+[NOTE]
+====
+The `server` module configures the shared parameters for generic HTTP handling, but does not enable any specific network protocol. You have to explicitly enable the protocols you want to support by enabling, for example, the <> for clear-text HTTP/1.1 support, or the <> for secure HTTP/2 support, etc.
+
+See also the xref:protocols/index.adoc[protocols section] for more information about the supported protocols.
+====
+
+[[server-http-config]]
+=== HTTP Configuration Properties
+
+The module properties to configure generic HTTP properties are listed below. Mostly they frequently apply to HTTP/1, HTTP/2 and HTTP/3, but some parameters are version specific:
+
+----
+include::{jetty-home}/modules/server.mod[tags=documentation-http-config]
+----
+
+Among the configurable properties, the most relevant are:
+
+`jetty.httpConfig.headerCacheSize`::
+The header cache is used when parsing HTTP/1 to more efficiently handle fields that are repeated in every request on a connection. If the server does not receive persistent connection or infrequent repeated fields, then there may be a performance gain in reducing the cache size. If large fields are frequently repeated, then a large cache may be beneficial.
+
+`jetty.httpConfig.delayDispatchUntilContent`::
+It is not uncommon for the network packets containing a request header to arrive before packets that contain the data of any request body. In such cases it may be beneficial for overall performance to delay dispatching the request to be handled until the first data packet arrives, as this may avoid blocking the handling thread. However, if minimum latency for receiving the request without content is important, then this parameter can be set to false.
+
+`jetty.httpConfig.sendServerVersion`::
+Whether you want to send the `Server` header in every HTTP response:
++
+[,screen,subs=normal]
+----
+HTTP/1.1 200 OK
+Content-Length: 0
+Server: Jetty({version})
+----
+
+[[server-config]]
+=== Server Configuration Properties
+
+The module properties to configure the Jetty server are:
+
+----
+include::{jetty-home}/modules/server.mod[tags=documentation-server-config]
+----
+
+Among the configurable properties, the most relevant are:
+
+`jetty.server.dumpAfterStart`::
+Whether to perform a `Server.dump()` operation after the `Server` has started.
+The output of the dump operation is sent to `System.err`.
+See also the xref:troubleshooting/index.adoc#dump[Jetty Server Dump] section for more information.
+
+`jetty.server.dumpBeforeStop`::
+Whether to perform a `Server.dump()` operation before the `Server` stops.
+The output of the dump operation is sent to `System.err`.
+See also the xref:troubleshooting/index.adoc#dump[Jetty Server Dump] section for more information.
+
+`jetty.server.stopAtShutdown`::
+Whether to call `Server.stop()` through a JVM shutdown hook when the JVM exits.
+
+[[server-compliance]]
+=== Server Compliance Properties
+
+The Jetty server strives to keep up with the latest https://en.wikipedia.org/wiki/Request_for_Comments[IETF RFC]s for compliance with internet specifications, which are periodically updated. When possible, Jetty will support backwards compatibility by providing compliance modes that can be configured to allow violations of the current specifications that may have been allowed in obsoleted specifications.
+The module properties to configure the Jetty server compliance are:
+
+----
+include::{jetty-home}/modules/server.mod[tags=documentation-server-compliance]
+----
+
+Among the configurable properties, the most relevant are:
+
+`jetty.httpConfig.compliance`::
+Configures the compliance to HTTP specifications.
+The value could be:
+
+* One of the predefined link:{javadoc-url}/org/eclipse/jetty/http/HttpCompliance.html[`HttpCompliance`] constants, such as `RFC7230` or `RFC2616`.
+For example: `jetty.httpConfig.compliance=RFC2616`.
+* A comma-separated list of violations to allow or forbid, as specified by the link:{javadoc-url}/org/eclipse/jetty/http/HttpCompliance.html#from(java.lang.String)[`HttpCompliance.from(String)`] method.
+For example, `jetty.httpConfig.compliance=RFC7230,MULTIPLE_CONTENT_LENGTHS` means that the HTTP compliance is that defined by `RFC7230`, but also allows the `HttpCompliance.Violation.MULTIPLE_CONTENT_LENGTHS`, so that requests that have multiple `Content-Length` headers are accepted (they would be rejected when using just `HttpCompliance.RFC7230`).
++
+For more information about `HttpCompliance` see also xref:programming-guide:server/compliance.adoc#http[this section].
+
+`jetty.httpConfig.uriCompliance`::
+Configures the compliance to URI specifications.
+The value could be:
+
+* One of the predefined link:{javadoc-url}/org/eclipse/jetty/http/UriCompliance.html[`UriCompliance`] constants, such as `DEFAULT` or `RFC3986`.
+For example: `jetty.httpConfig.compliance=RFC3986`.
+* A comma-separated list of violations to allow or forbid, as specified by the link:{javadoc-url}/org/eclipse/jetty/http/UriCompliance.html#from(java.lang.String)[`UriCompliance.from(String)`] method.
+For example, `jetty.httpConfig.uriCompliance=RFC3986,-AMBIGUOUS_PATH_SEPARATOR` means that the URI compliance is that defined by `RFC3986`, but also does not allow the `UriCompliance.Violation.AMBIGUOUS_PATH_SEPARATOR`, so that requests that have URIs such as `/foo/bar%2Fbaz` (where `%2F` is the URL-encoded `/` character) are rejected (they would be accepted when using just `UriCompliance.RFC3986`).
++
+For more information about `UriCompliance` see also xref:programming-guide:server/compliance.adoc#uri[this section].
+
+`jetty.httpConfig.requestCookieCompliance`::
+`jetty.httpConfig.responseCookieCompliance`::
+Configures the compliance to HTTP cookie specifications.
+The value could be:
+
+* One of the predefined link:{javadoc-url}/org/eclipse/jetty/http/CookieCompliance.html[`CookieCompliance`] constants, such as `RFC6265`.
+For example: `jetty.httpConfig.compliance=RFC6265`.
+* A comma-separated list of violations to allow or forbid, as specified by the link:{javadoc-url}/org/eclipse/jetty/http/CookieCompliance.html#from(java.lang.String)[`CookieCompliance.from(String)`] method.
+For example, `jetty.httpConfig.requestCookieCompliance=RFC6265,-RESERVED_NAMES_NOT_DOLLAR_PREFIXED` means that the cookie compliance is that defined by `RFC6265`, but also does not allow the `CookieCompliance.Violation.RESERVED_NAMES_NOT_DOLLAR_PREFIXED`, so that requests that have cookie headers such as `Cookie: $foo=bar` are rejected (they would be accepted when using just `CookieCompliance.RFC6265`).
++
+For more information about `CookieCompliance` see also xref:programming-guide:server/compliance.adoc#cookie[this section].
+
+[[scheduler-config]]
+=== Server Scheduler Configuration Properties
+
+The module properties to configure the Jetty server scheduler are:
+
+----
+include::{jetty-home}/modules/server.mod[tags=documentation-scheduler-config]
+----
+
+[[ssl]]
+== Module `ssl`
+
+The `ssl` module provides the secure connector, and allows you to configure the KeyStore properties and the TLS parameters, and depends on the <>.
+
+[[ssl-connector]]
+=== Secure Connector Properties
+
+The module properties to configure the secure connector are:
+
+----
+include::{jetty-home}/modules/ssl.mod[tags=documentation-connector]
+----
+
+Among the configurable properties, the most relevant are:
+
+`jetty.ssl.port`::
+The network port that Jetty listens to for secure connections -- default `8443`.
+`jetty.ssl.idleTimeout`::
+The amount of time a connection can be idle (i.e. no bytes received and no bytes sent) until the server decides to close it to save resources -- default `30000` milliseconds.
+`jetty.ssl.acceptors`::
+The number of threads that compete to accept connections -- default 1. Use -1 to let the accept heuristic decides the value; the current heuristic calculates a value based on the number of cores).
+Refer to <> for more information about acceptor threads.
+`jetty.ssl.selectors`::
+The number of NIO selectors (with an associated thread) that manage connections -- default -1 (i.e. a select heuristic decides the value; the current heuristic calculates a value based on the number of cores).
+Refer to <> for more information about selector threads.
+
+The module properties to configure the KeyStore and TLS parameters are:
+
+----
+include::{jetty-home}/modules/ssl.mod[tags=documentation-ssl-context]
+----
+
+[[ssl-keystore-tls]]
+=== KeyStore Properties and TLS Properties
+
+Among the configurable properties, the most relevant are:
+
+`jetty.sslContext.keyStorePath`::
+The KeyStore path on the file system, either an absolute path or a relative path to `$JETTY_BASE` -- defaults to `$JETTY_BASE/etc/keystore.p12`.
+`jetty.sslContext.keyStorePassword`::
+The KeyStore password, which you want to explicitly configure.
+The password may be obfuscated with the xref:jaas/index.adoc#og-password[Jetty Password Tool].
+
+If you need to configure client certificate authentication, you want to configure one of these properties (they are mutually exclusive):
+
+`jetty.sslContext.needClientAuth`::
+Whether client certificate authentication should be required.
+`jetty.sslContext.wantClientAuth`::
+Whether client certificate authentication should be requested.
+
+If you configure client certificate authentication, you need to configure and distribute a client KeyStore as explained in xref:keystore/index.adoc#client-authn[this section].
+
+[[ssl-reload]]
+== Module `ssl-reload`
+
+The `ssl-reload` module provides a periodic scanning of the directory where the KeyStore file resides.
+When the scanning detects a change to the KeyStore file, the correspondent `SslContextFactory.Server` component is reloaded with the new KeyStore configuration.
+
+The module properties are:
+
+----
+include::{jetty-home}/modules/ssl-reload.mod[tags=documentation]
+----
+
+[[test-keystore]]
+== Module `test-keystore`
+
+The `test-keystore` module creates on-the-fly a KeyStore containing a self-signed certificate for domain `localhost`.
+The KeyStore file is automatically deleted when the JVM exits, and re-created when you restart Jetty, to enforce the fact that it is a _test_ KeyStore that should not be reused if not for testing.
+
+The module file is `$JETTY_HOME/modules/test-keystore.mod`:
+
+----
+include::{jetty-home}/modules/test-keystore.mod[]
+----
+
+Note how properties `jetty.sslContext.keyStorePath` and `jetty.sslContext.keyStorePassword` are configured, only if not already set (via the `?=` operator), directly in the module file, rather than in a `+*.ini+` file.
+This is done to avoid that these properties accidentally overwrite a real KeyStore configuration.
+
+[[threadpool]]
+== Module `threadpool`
+
+The `threadpool` module allows you to configure the server-wide thread pool.
+
+The thread pool creates threads on demand up to `maxThreads`, and idles them out if they are not used.
+
+Since Jetty uses the thread pool internally to execute critical tasks, it is not recommended to constrain the thread pool to small values of `maxThreads` with the purpose of limiting HTTP request concurrency, as this could very likely cause a server lockup when Jetty needs to run a critical task but there are no threads available.
+Start with the default value of `maxThreads`, and tune for larger values if needed.
+
+The module properties to configure the thread pool are:
+
+----
+include::{jetty-home}/modules/threadpool.mod[tags=documentation]
+----
+
+Among the configurable properties, the most relevant are:
+
+`jetty.threadPool.namePrefix`::
+The name prefix to use for the thread names.
+
+`jetty.threadPool.detailedDump`::
+Whether the thread pool should dump the whole stack trace of each thread, or just the topmost stack frame -- defaults to `false`.
+
+`jetty.threadPool.idleTimeout`::
+The time, in milliseconds, after which an idle thread is released from the pool -- defaults to 60000, i.e. 60 seconds.
+
+`jetty.threadPool.maxThreads`::
+The max number of threads pooled by the thread pool -- defaults to 200.
+
+If you want to use virtual threads, introduced as a preview feature in Java 19 and Java 20, and become an official feature since Java 21, use the following modules:
+
+* The <> Jetty module for Java 21 or later.
+* The <> Jetty module for Java 19 and Java 20.
+
+See also the xref:server/index.adoc#threadpool[section about configuring the thread pool].
+
+[[threadpool-virtual]]
+== Module `threadpool-virtual`
+
+The `threadpool-virtual` module allows you to configure the server-wide thread pool, similarly to what you can do with the <> Jetty module, but also specify to use virtual threads, introduced as an official feature since Java 21.
+
+CAUTION: Only use this module if you are using Java 21 or later.
+If you are using Java 19 or Java 20, use the <> Jetty module instead.
+
+Refer to the <> Jetty module for the general features provided by that Jetty module that also this Jetty module provides.
+
+The module properties to configure the thread pool are:
+
+----
+include::{jetty-home}/modules/threadpool-virtual.mod[tags=documentation]
+----
+
+The specific properties to configure virtual threads are:
+
+`jetty.threadPool.virtual.namePrefix`::
+The name prefix to use for the virtual thread names.
+
+`jetty.threadPool.virtual.inheritInheritableThreadLocals`::
+Whether virtual threads inherit the values of `InheritableThreadLocal` variables.
+
+[[threadpool-virtual-preview]]
+== Module `threadpool-virtual-preview`
+
+The `threadpool-virtual-preview` module allows you to configure the server-wide thread pool, similarly to what you can do with the <> Jetty module, but also specify to use virtual threads, introduced as a preview feature in Java 19 and in Java 20.
+
+CAUTION: Only use this module if you are using Java 19 or Java 20.
+If you are using Java 21 or later, use the <> Jetty module instead.
+
+NOTE: To enable preview features, this module needs to specify the `+--enable-preview+` command line option using the xref:modules/index.adoc#directive-exec[[exec\] directive], and as such it will fork another JVM.
+
+Refer to the <> Jetty module for the general features provided by that Jetty module that also this Jetty module provides.
+
+The module properties to configure the thread pool are:
+
+----
+include::{jetty-home}/modules/threadpool-virtual-preview.mod[tags=documentation]
+----
+
+The specific properties to configure virtual threads are:
+
+`jetty.threadPool.virtual.namePrefix`::
+The name prefix to use for the virtual thread names.
+
+`jetty.threadPool.virtual.allowSetThreadLocals`::
+Whether virtual threads are allowed to set thread locals.
+
+`jetty.threadPool.virtual.inheritInheritableThreadLocals`::
+Whether virtual threads inherit the values of `InheritableThreadLocal` variables.
+
+[[well-known]]
+== Module `well-known`
+
+The `well-known` Jetty module creates a `ResourceHandler` deployed at the `/.well-known` context path which serves files from a directory.
+By default, the directory created at `$JETTY_BASE/.well-known` is used, but it can be configured from `well-known.ini` to anywhere in the filesystem.
+Note that the `.well-known` directory may be seen as a hidden directory by the filesystem.
+
+The concept of well-known URIs has been defined in https://datatracker.ietf.org/doc/html/rfc5785[RFC5785].
+This module can be used for things like the automatic renewal of https://letsencrypt.org/[Let's Encrypt] certificates.
+See https://www.iana.org/assignments/well-known-uris/well-known-uris.xhtml[IANA Well-Known URIs] for more possible examples of how this can be used.
+
+The module properties are:
+
+----
+include::{jetty-home}/modules/well-known.mod[tags=documentation]
+----
diff --git a/documentation/jetty/modules/operations-guide/pages/protocols/index.adoc b/documentation/jetty/modules/operations-guide/pages/protocols/index.adoc
new file mode 100644
index 00000000000..19c5e2fb21f
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/protocols/index.adoc
@@ -0,0 +1,1093 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= Jetty Connectors and Protocols
+
+Connectors are the network components through which Jetty accepts incoming network connections.
+
+Each connector listens on a network port and can be configured with `ConnectionFactory` components that _understand_ one or more network protocols.
+
+Understanding a protocol means that the connector is able to interpret incoming network bytes (for example, the bytes that represent an HTTP/1.1 request) and convert them into more abstract objects (for example an `HttpServletRequest` object) that are then processed by applications.
+Conversely, an abstract object (for example an `HttpServletResponse`) is converted into the correspondent outgoing network bytes (the bytes that represent an HTTP/1.1 response).
+
+Like other Jetty components, connectors are enabled and configured by enabling and configuring the correspondent Jetty module.
+
+IMPORTANT: Recall that you must always issue the commands to enable Jetty modules from within the `$JETTY_BASE` directory, and that the Jetty module configuration files are in the `$JETTY_BASE/start.d/` directory.
+
+You can obtain the list of connector-related modules in this way:
+
+----
+$ java -jar $JETTY_HOME/start.jar --list-modules=connector
+----
+
+[[http]]
+== Clear-Text HTTP/1.1
+
+Clear text HTTP/1.1 is enabled with the `http` Jetty module with the following command (issued from within the `$JETTY_BASE` directory):
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=http
+----
+----
+INFO : mkdir ${jetty.base}/start.d
+INFO : server transitively enabled, ini template available with --add-module=server
+INFO : logging-jetty transitively enabled
+INFO : http initialized in ${jetty.base}/start.d/http.ini
+INFO : resources transitively enabled
+INFO : threadpool transitively enabled, ini template available with --add-module=threadpool
+INFO : logging/slf4j dynamic dependency of logging-jetty
+INFO : bytebufferpool transitively enabled, ini template available with --add-module=bytebufferpool
+INFO : mkdir ${jetty.base}/resources
+INFO : copy ${jetty.home}/modules/logging/jetty/resources/jetty-logging.properties to ${jetty.base}/resources/jetty-logging.properties
+INFO : Base directory was modified
+----
+
+After having enabled the `http` module, the `$JETTY_BASE` directory looks like this:
+
+[source,subs=quotes]
+----
+JETTY_BASE
+├── resources
+│ └── jetty-logging.properties
+└── start.d
+ └── #http.ini#
+----
+
+The `http.ini` file is the file that you want to edit to configure network and protocol parameters -- for more details see xref:modules/standard.adoc#http[this section].
+
+Note that the `http` Jetty module depends on the `server` Jetty module.
+
+Some parameters that you may want to configure are in fact common HTTP parameters that are applied not only for clear-text HTTP/1.1, but also for secure HTTP/1.1 or for clear-text HTTP/2 or for encrypted HTTP/2, or for HTTP/3, and these configuration parameters may be present in the `server` module configuration file.
+
+You can force the creation of the `server.ini` file via:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=server
+----
+
+Now the `$JETTY_BASE` directory looks like this:
+
+[source]
+----
+JETTY_BASE
+├── resources
+│ └── jetty-logging.properties
+└── start.d
+ ├── http.ini
+ └── server.ini
+----
+
+Now you can edit the `server.ini` file -- for more details see xref:modules/standard.adoc#server[this section].
+
+[[https]]
+== Secure HTTP/1.1
+
+Secure HTTP/1.1 is enabled with both the `ssl` and `https` Jetty modules with the following command (issued from within the `$JETTY_BASE` directory):
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=ssl,https
+----
+
+[jetty%nowrap]
+....
+[jetty]
+args=--add-modules=ssl,https
+....
+
+The command above enables the `ssl` module, that provides the secure network connector, the KeyStore configuration and TLS configuration -- for more details see <>.
+Then, the xref:modules/standard.adoc#https[`https` module] adds HTTP/1.1 as the protocol secured by TLS.
+
+The `$JETTY_BASE` directory looks like this:
+
+[source]
+----
+$JETTY_BASE
+├── resources
+│ └── jetty-logging.properties
+└── start.d
+ ├── https.ini
+ └── ssl.ini
+----
+
+Note that the KeyStore file is missing, because you have to provide one with the cryptographic material you want (read xref:keystore/index.adoc[this section] to create your own KeyStore).
+You need to configure these two properties by editing `ssl.ini`:
+
+* `jetty.sslContext.keyStorePath`
+* `jetty.sslContext.keyStorePassword`
+
+As a quick example, you can enable the xref:modules/standard.adoc#test-keystore[`test-keystore` module], that creates on-the-fly a KeyStore containing a self-signed certificate:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=test-keystore
+----
+
+[jetty%nowrap]
+....
+[jetty]
+setupArgs=--add-modules=ssl,https
+args=--add-modules=test-keystore
+....
+
+The `$JETTY_BASE` directory is now:
+
+[source,subs=quotes]
+----
+├── etc
+│ └── #test-keystore.p12#
+├── resources
+│ └── jetty-logging.properties
+└── start.d
+ ├── https.ini
+ ├── ssl.ini
+ └── test-keystore.ini
+----
+
+Starting Jetty yields:
+
+----
+$ java -jar $JETTY_HOME/start.jar
+----
+
+[jetty%nowrap]
+....
+[jetty]
+setupArgs=--add-modules=ssl,https,test-keystore
+highlight=(\{.*:8443})
+....
+
+Note how Jetty is listening on port `8443` for the secure HTTP/1.1 protocol.
+
+[WARNING]
+====
+If you point your browser at `+https://localhost:8443/+` you will get a warning from the browser about a "potential security risk ahead", or that "your connection is not private", or similar message depending on the browser.
+
+This is normal because the certificate contained in `test-keystore.p12` is self-signed -- and as such not signed by a recognized certificate authority -- and therefore browsers do not trust it.
+====
+
+[[http2]]
+== Configuring HTTP/2
+
+https://tools.ietf.org/html/rfc7540[HTTP/2] is the successor of the HTTP/1.1 protocol, but it is quite different from HTTP/1.1: where HTTP/1.1 is a duplex, text-based protocol, HTTP/2 is a multiplex, binary protocol.
+
+Because of these fundamental differences, a client and a server need to _negotiate_ what version of the HTTP protocol they speak, based on what versions each side supports.
+
+To ensure maximum compatibility, and reduce the possibility that an intermediary that only understands HTTP/1.1 will close the connection when receiving unrecognized HTTP/2 bytes, HTTP/2 is typically deployed over secure connections, using the TLS protocol to wrap HTTP/2.
+
+IMPORTANT: Browsers only support secure HTTP/2.
+
+The protocol negotiation is performed by the https://tools.ietf.org/html/rfc7301[ALPN TLS extension]: the client advertises the list of protocols it can speak, and the server communicates to the client the protocol chosen by the server.
+
+For example, you can have a client that only supports HTTP/1.1 and a server that supports both HTTP/1.1 and HTTP/2:
+
+[plantuml]
+----
+skinparam backgroundColor transparent
+skinparam monochrome true
+skinparam shadowing false
+
+participant "client\nsupports\nhttp/1.1" as client
+participant "server\nsupports\nhttp/1.1 & http/2" as server
+
+group TLS handshake
+client -> server : ClientHello (alpn=[http/1.1])
+server -> server : picks http/1.1
+server -> client : ServerHello (alpn=http/1.1)
+...rest of TLS handshake...
+end
+group TLS HTTP/1.1
+client -> server : HTTP/1.1 GET
+server -> client : HTTP/1.1 200
+end
+----
+
+Nowadays, it's common that both clients and servers support HTTP/2, so servers prefer HTTP/2 as the protocol to speak:
+
+[plantuml]
+----
+skinparam backgroundColor transparent
+skinparam monochrome true
+skinparam shadowing false
+
+participant "client\nsupports\nhttp/1.1 & http/2" as client
+participant "server\nsupports\nhttp/1.1 & http/2" as server
+
+group TLS handshake
+client -> server : ClientHello (alpn=[http/1.1,h2])
+server -> server : picks http/2
+server -> client : ServerHello (alpn=h2)
+...rest of TLS handshake...
+end
+group TLS HTTP/2
+client -> server : HTTP/2 GET
+server -> client : HTTP/2 200
+end
+----
+
+When you configure a connector with the HTTP/2 protocol, you typically want to also configure the HTTP/1.1 protocol.
+The reason to configure both protocols is that you typically do not control the clients: for example an old browser that does not support HTTP/2, or a monitoring console that performs requests using HTTP/1.1, or a heartbeat service that performs a single HTTP/1.0 request to verify that the server is alive.
+
+== Secure vs Clear-Text HTTP/2
+
+Deciding whether you want to configure Jetty with <> or <> depends on your use case.
+
+You want to configure secure HTTP/2 when Jetty is exposed directly to browsers, because browsers only support secure HTTP/2.
+
+[plantuml]
+----
+skinparam backgroundColor transparent
+skinparam monochrome true
+skinparam shadowing false
+skinparam roundCorner 10
+
+rectangle browser
+cloud internet
+rectangle jetty
+
+jetty <--> internet : TLS+HTTP/2
+internet <--> browser : TLS+HTTP/2
+----
+
+You may configure clear-text HTTP/2 (mostly for performance reasons) if you offload TLS at a load balancer (for example, https://haproxy.org/[HAProxy]) or at a reverse proxy (for example, https://nginx.org/[nginx]).
+
+[plantuml]
+----
+skinparam backgroundColor transparent
+skinparam monochrome true
+skinparam shadowing false
+skinparam roundCorner 10
+
+rectangle browser
+cloud internet
+rectangle haproxy
+rectangle jetty
+
+note right of haproxy: TLS offload
+
+jetty <--> haproxy : HTTP/2 (clear-text)
+haproxy <--> internet : TLS+HTTP/2
+internet <--> browser : TLS+HTTP/2
+----
+
+You may configure clear-text HTTP/2 (mostly for performance reasons) to call microservices deployed to different Jetty servers (although you may want to use secure HTTP/2 for confidentiality reasons).
+
+[plantuml]
+----
+skinparam backgroundColor transparent
+skinparam monochrome true
+skinparam shadowing false
+skinparam roundCorner 10
+
+rectangle browser
+cloud internet
+rectangle haproxy
+rectangle jetty
+rectangle microservice1
+rectangle microservice2
+rectangle microservice3
+
+note right of haproxy: TLS offload
+
+internet <--> browser : TLS+HTTP/2
+haproxy <--> internet : TLS+HTTP/2
+jetty <--> haproxy : HTTP/2 (clear-text)
+microservice1 <--> jetty : HTTP/2
+microservice2 <--> jetty : HTTP/2
+microservice3 <--> jetty : HTTP/2
+microservice2 <--> microservice3 : HTTP/2
+microservice1 <--> microservice3 : HTTP/2
+----
+
+[[http2s]]
+== Secure HTTP/2
+
+When you enable secure HTTP/2 you typically want to enable also secure HTTP/1.1, for backwards compatibility reasons: in this way, old browsers or other clients that do not support HTTP/2 will be able to connect to your server.
+
+You need to enable:
+
+* the `ssl` Jetty module, which provides the secure connector and the KeyStore and TLS configuration
+* the `http2` Jetty module, which adds ALPN handling and adds the HTTP/2 protocol to the secured connector
+* optionally, the `https` Jetty module, which adds the HTTP/1.1 protocol to the secured connector
+
+Use the following command (issued from within the `$JETTY_BASE` directory):
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=ssl,http2,https
+----
+
+As when enabling the `https` Jetty module, you need a valid KeyStore (read xref:keystore/index.adoc[this section] to create your own KeyStore).
+
+As a quick example, you can enable the xref:modules/standard.adoc#test-keystore[`test-keystore` module], that creates on-the-fly a KeyStore containing a self-signed certificate:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=test-keystore
+----
+
+Starting Jetty yields:
+
+----
+$ java -jar $JETTY_HOME/start.jar
+----
+
+[jetty%nowrap]
+....
+[jetty]
+setupArgs=--add-modules=ssl,http2,https,test-keystore
+highlight=(\{.*:8443})
+....
+
+Note how Jetty is listening on port `8443` and the protocols supported are the sequence `(ssl, alpn, h2, http/1.1)`.
+
+The (ordered) list of protocols after `alpn` are the _application protocols_, in the example above `(h2, http/1.1)`.
+
+When a new connection is accepted by the connector, Jetty first interprets the TLS bytes, then it handles the ALPN negotiation knowing that the application protocols are (in order) `h2` and then `http/1.1`.
+
+You can customize the list of application protocols and the default protocol to use in case the ALPN negotiation fails by editing the xref:modules/standard.adoc#alpn[`alpn` module] properties.
+
+The HTTP/2 protocol parameters can be configured by editing the xref:modules/standard.adoc#http2[`http2` module] properties.
+
+[[http2c]]
+== Clear-Text HTTP/2
+
+When you enable clear-text HTTP/2 you typically want to enable also clear-text HTTP/1.1, for backwards compatibility reasons and to allow clients to https://tools.ietf.org/html/rfc7540#section-3.2[upgrade] from HTTP/1.1 to HTTP/2.
+
+You need to enable:
+
+* the `http` Jetty module, which provides the clear-text connector and adds the HTTP/1.1 protocol to the clear-text connector
+* the `http2c` Jetty module, which adds the HTTP/2 protocol to the clear-text connector
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=http,http2c
+----
+
+Starting Jetty yields:
+
+----
+$ java -jar $JETTY_HOME/start.jar
+----
+
+[jetty%nowrap]
+....
+[jetty]
+setupArgs=--add-modules=http,http2c
+highlight=(\{.+:8080})
+....
+
+Note how Jetty is listening on port `8080` and the protocols supported are HTTP/1.1 and `h2c` (i.e. clear-text HTTP/2).
+
+With this configuration, browsers and client applications will be able to connect to port `8080` using:
+
+* HTTP/1.1 directly (e.g. `curl --http1.1 ++http://localhost:8080++`):
+----
+GET / HTTP/1.1
+Host: localhost:8080
+----
+* HTTP/1.1 with upgrade to HTTP/2 (e.g. `curl --http2 ++http://localhost:8080++`):
+----
+GET / HTTP/1.1
+Host: localhost:8080
+Connection: Upgrade, HTTP2-Settings
+Upgrade: h2c
+HTTP2-Settings:
+----
+* HTTP/2 directly (e.g. `curl --http2-prior-knowledge ++http://localhost:8080++`):
+----
+50 52 49 20 2a 20 48 54 54 50 2f 32 2e 30 0d 0a
+0d 0a 53 4d 0d 0a 0d 0a 00 00 12 04 00 00 00 00
+00 00 03 00 00 00 64 00 04 40 00 00 00 00 02 00
+00 00 00 00 00 1e 01 05 00 00 00 01 82 84 86 41
+8a a0 e4 1d 13 9d 09 b8 f0 1e 07 7a 88 25 b6 50
+c3 ab b8 f2 e0 53 03 2a 2f 2a
+----
+
+The HTTP/2 protocol parameters can be configured by editing the xref:modules/standard.adoc#http2c[`http2c` module] properties.
+
+[[http3]]
+== HTTP/3
+
+When you enable support for the HTTP/3 protocol, by default the secure HTTP/2 protocol is also enabled, so that browsers or clients that do not support HTTP/3 will be able to connect to your server.
+
+You need to enable:
+
+* the `ssl` Jetty module, which provides the KeyStore and TLS configuration
+* the `http3` Jetty module, which adds the HTTP/3 protocol on the HTTP/3 connector
+
+Use the following command (issued from within the `$JETTY_BASE` directory):
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=ssl,http3
+----
+
+Enabling any module Jetty module that supports secure network communication requires a valid KeyStore (read xref:keystore/index.adoc[this section] to create your own KeyStore), that, as a quick example, you can enable with the xref:modules/standard.adoc#test-keystore[`test-keystore` module], that creates on-the-fly a KeyStore containing a self-signed certificate:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=test-keystore
+----
+
+Starting Jetty yields:
+
+----
+$ java -jar $JETTY_HOME/start.jar
+----
+
+[jetty%nowrap]
+....
+[jetty]
+setupArgs=--approve-all-licenses --add-modules=ssl,http3,test-keystore
+highlight=(\{.*:8444})
+....
+
+Note how Jetty is listening on port `8443` for HTTP/2 and on port `8444` for HTTP/3.
+
+The HTTP/3 protocol parameters can be configured by editing the xref:modules/standard.adoc#http3[`http3` module] properties.
+
+[[ssl]]
+== Configuring Secure Protocols
+
+Secure protocols are normal protocols such as HTTP/1.1, HTTP/2 or WebSocket that are wrapped by the https://en.wikipedia.org/wiki/Transport_Layer_Security[TLS protocol].
+Any network protocol based on TCP can be wrapped with TLS.
+
+QUIC, the protocol based on UDP that transports HTTP/3, uses TLS messages but not the TLS protocol framing.
+
+The `https` scheme used in URIs really means `tls+http/1.1` (or `tls+http/2`, or `quic+http/3`) and similarly the `wss` scheme used in URIs really means `tls+websocket`, etc.
+Senders wrap the underlying protocol bytes (e.g. HTTP bytes or WebSocket bytes) with the TLS protocol, while receivers first interpret the TLS protocol to obtain the underlying protocol bytes, and then interpret the wrapped bytes.
+
+The xref:modules/standard.adoc#ssl[`ssl` Jetty module] allows you to configure a secure network connector; if other modules require encryption, they declare a dependency on the `ssl` module.
+
+It is the job of other Jetty modules to configure the wrapped protocol.
+For example, it is the <> that configures the wrapped protocol to be HTTP/1.1.
+Similarly, it is the <> that configures the wrapped protocol to be HTTP/2.
+If you enable _both_ the `https` and the `http2` module, you will have a single secure connector that will be able to interpret both HTTP/1.1 and HTTP/2.
+
+TIP: Recall from the xref:modules/index.adoc[section about modules], that only modules that are explicitly enabled get their module configuration file (`+*.ini+`) saved in `$JETTY_BASE/start.d/`, and you want `$JETTY_BASE/start.d/ssl.ini` to be present so that you can configure the connector properties, the KeyStore properties and the TLS properties.
+
+[[ssl-customize]]
+=== Customizing KeyStore and SSL/TLS Configuration
+
+Secure protocols have a slightly more complicated configuration since they require to configure a _KeyStore_.
+Refer to the xref:keystore/index.adoc[KeyStore section] for more information about how to create and manage a KeyStore.
+
+For simple cases, you only need to configure the KeyStore path and KeyStore password as explained in xref:modules/standard.adoc#ssl-keystore-tls[this section].
+
+For more advanced configuration you may want to configure the TLS protocol versions, or the ciphers to include/exclude, etc.
+The correct way of doing this is to create a custom xref:xml/index.adoc[Jetty XML file] and reference it in `$JETTY_BASE/start.d/ssl.ini`:
+
+.ssl.ini
+[source,subs=verbatim]
+----
+jetty.sslContext.keyStorePassword=my_passwd! <1>
+etc/tls-config.xml <2>
+----
+<1> Configures the `jetty.sslContext.keyStorePassword` property with the KeyStore password.
+<2> References your newly created `$JETTY_BASE/etc/tls-config.xml`.
+
+The `ssl.ini` file above only shows the lines that are not commented out (you can leave the lines that are commented unmodified for future reference).
+
+You want to create the `$JETTY_BASE/etc/tls-config.xml` with the following template content:
+
+.tls-config.xml
+[,xml,subs=verbatim]
+----
+
+
+
+
+ [
+ ... <1>
+ ]
+
+----
+<1> Here goes your advanced configuration.
+
+The `tls-config.xml` file references the `sslContextFactory` component (created by the `ssl` Jetty module) that configures the KeyStore and TLS parameters, so that you can now call its APIs via XML, and you will have full flexibility for any advanced configuration you want (see below for few examples).
+
+Refer to the link:{javadoc-url}/org/eclipse/jetty/util/ssl/SslContextFactory.html[SslContextFactory javadocs] for the list of methods that you can call through the Jetty XML file.
+
+CAUTION: Use module properties whenever possible, and only resort to use a Jetty XML file for advanced configuration that you cannot do using module properties.
+
+[[ssl-customize-versions]]
+==== Customizing SSL/TLS Protocol Versions
+
+By default, the SSL protocols (SSL, SSLv2, SSLv3, etc.) are already excluded because they are vulnerable.
+To explicitly add the exclusion of TLSv1.0 and TLSv1.1 (that are also vulnerable -- which leaves only TLSv1.2 and TLSv1.3 available), you want to use this XML:
+
+.tls-config.xml
+[,xml]
+----
+
+
+
+
+ [
+ ]
+
+
+ - TLSv1.0
+ - TLSv1.1
+
+
+
+
+
+----
+
+[[ssl-customize-ciphers]]
+==== Customizing SSL/TLS Ciphers
+
+You can precisely set the list of excluded ciphers, completely overriding Jetty's default, with this XML:
+
+.tls-config.xml
+[,xml]
+----
+
+
+
+
+ [
+ ]
+
+ - ^TLS_RSA_.*$
+ - ^.*_RSA_.*_(MD5|SHA|SHA1)$
+ - ^.*_DHE_RSA_.*$
+ - SSL_RSA_WITH_DES_CBC_SHA
+ - SSL_DHE_RSA_WITH_DES_CBC_SHA
+ - SSL_DHE_DSS_WITH_DES_CBC_SHA
+ - SSL_RSA_EXPORT_WITH_RC4_40_MD5
+ - SSL_RSA_EXPORT_WITH_DES40_CBC_SHA
+ - SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
+ - SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
+
+
+
+
+----
+
+Note how each array item specifies a _regular expression_ that matches multiple ciphers, or specifies a precise cipher to exclude.
+
+You can choose to create multiple XML files, and reference them all from `$JETTY_BASE/start.d/ssl.ini`, or put all your custom configurations in a single XML file.
+
+[[ssl-renew]]
+=== Renewing the Certificates
+
+When you create a certificate, you must specify for how many days it is valid.
+
+The typical validity is 90 days, and while this period may seem short, it has two advantages:
+
+* Reduces the risk in case of compromised/stolen keys.
+* Encourages automation, i.e. certificate renewal performed by automated tools (rather than manually) at scheduled times.
+
+To renew a certificate, you must go through the xref:keystore/index.adoc#create[same steps] you followed to create the certificate the first time, and then you can <> without the need to stop Jetty.
+
+[[ssl-reload]]
+=== Watching and Reloading the KeyStore
+
+Jetty can be configured to monitor the directory of the KeyStore file, and reload the `SslContextFactory` component if the KeyStore file changed.
+
+This feature can be enabled by activating the `ssl-reload` Jetty module:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=ssl-reload
+----
+
+For more information about the configuration of the `ssl-reload` Jetty module, see xref:modules/standard.adoc#ssl-reload[this section].
+
+[[ssl-conscrypt]]
+=== Using Conscrypt as SSL/TLS Provider
+
+If not explicitly configured, the TLS implementation is provided by the JDK you are using at runtime.
+
+OpenJDK's vendors may replace the default TLS provider with their own, but you can also explicitly configure an alternative TLS provider.
+
+The standard TLS provider from OpenJDK is implemented in Java (no native code), and its performance is not optimal, both in CPU usage and memory usage.
+
+A faster alternative, implemented natively, is Google's https://github.com/google/conscrypt/[Conscrypt], which is built on https://boringssl.googlesource.com/boringssl/[BoringSSL], which is Google's fork of https://www.openssl.org/[OpenSSL].
+
+CAUTION: As Conscrypt eventually binds to a native library, there is a higher risk that a bug in Conscrypt or in the native library causes a JVM crash, while the Java implementation will not cause a JVM crash.
+
+To use Conscrypt as the TLS provider just enable the `conscrypt` Jetty module:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=conscrypt
+----
+
+[[ssl-sni]]
+=== Configuring SNI
+
+Server Name Indication (SNI) is a TLS extension that clients send to indicate what domain they want to connect to during the initial TLS handshake.
+
+Modern TLS clients (e.g. browsers) always send the SNI extension; however, older TLS clients may not send the SNI extension.
+
+Being able to handle the SNI is important when you have xref:deploy/index.adoc#virtual-hosts[virtual hosts] and a KeyStore with multiple certificates, one for each domain.
+
+For example, you may have deployed over a secure connector two web applications, both at context path `/`, one at virtual host `one.com` and one at virtual host `two.net`.
+The KeyStore contains two certificates, one for `one.com` and one for `two.net`.
+
+There are three `ssl` module properties that control the SNI behavior on the server: one that works at the TLS level, and two that works at the HTTP level.
+
+The property that works at the TLS level is:
+
+`jetty.sslContext.sniRequired`::
+Whether SNI is required at the TLS level, defaults to `false`.
+Its behavior is explained by the following table:
++
+.Behavior of the `jetty.sslContext.sniRequired` property
+[cols="3*a"]
+|===
+|
+| `sniRequired=false`
+| `sniRequired=true`
+
+| SNI = `null`
+| client receives default certificate
+| client receives TLS failure
+
+| SNI = `wrong.org`
+| client receives default certificate
+| client receives TLS failure
+
+| SNI = `one.com`
+| client receives `one.com` certificate
+| client receives `one.com` certificate
+|===
++
+[WARNING]
+====
+The _default certificate_ is the certificate returned by the TLS implementation in case there is no SNI match, and you should not rely on this certificate to be the same across Java vendors and versions, or Jetty versions, or TLS provider vendors and versions.
+
+In the example above it could be either the `one.com` certificate or the `two.net` certificate.
+====
+
+When `jetty.sslContext.sniRequired=true`, clients that don't send a valid SNI receive a TLS failure, and their attempt to connect to the server fails.
+The details of this failure may not be reported and could be difficult to figure out that the failure is related to an invalid SNI.
+
+For this reason, other two properties are defined at the HTTP level, so that clients can received an HTTP 400 response with more details about what went wrong while trying to connect to the server:
+
+`jetty.ssl.sniRequired`::
+Whether SNI is required at the HTTP level, defaults to `false`.
+Its behavior is similar to the `jetty.sslContext.sniRequired` property above, and is explained by the following table:
++
+.Behavior of the `jetty.ssl.sniRequired` property
+[cols=3*a]
+|===
+|
+| `sniRequired=false`
+| `sniRequired=true`
+
+| SNI = `null`
+| Accept
+| Reject: 400 Bad Request
+
+| SNI = `wrong.org`
+| Accept
+| Reject: 400 Bad Request
+
+| SNI = `one.com`
+| Accept
+| Accept
+|===
+
+When `jetty.ssl.sniRequired=true`, the SNI is matched against the certificate sent to the client, and only if there is a match the request is accepted.
+
+When the request is accepted, there could be an additional check controlled by the following property:
+
+`jetty.ssl.sniHostCheck`::
+Whether the certificate sent to the client matches the `Host` header, defaults to `true`.
+Its behavior is explained by the following table:
++
+.Behavior of the `jetty.ssl.sniHostCheck` property
+[cols="3*a"]
+|===
+|
+| `sniHostCheck=false`
+| `sniHostCheck=true`
+
+| certificate = `one.com` +
+`Host: wrong.org`
+| Accept
+| Reject: 400 Bad Request
+
+| certificate = `one.com` +
+`Host: one.com`
+| Accept
+| Accept
+|===
+
+In the normal case with the default server configuration, for a TLS clients that sends SNI, and then sends an HTTP request with the correct `Host` header, Jetty will pick the correct certificate from the KeyStore based on the SNI received from the client, and accept the request.
+
+Accepting the request does not mean that the request is responded with an HTTP 200 OK, but just that the request passed successfully the SNI checks and will be processed by the server.
+If the request URI is for a resource that does not exist, the response will likely be a 404 Not Found.
+
+You may modify the default values of the SNI properties if you want stricter control over old/broken TLS clients or bad HTTP requests.
+
+[[proxy]]
+== Jetty Behind a Load Balancer or Reverse Proxy
+
+You may need to configure one or more Jetty instances behind an _intermediary_, typically a load balancer such as https://haproxy.org[HAProxy], or a reverse proxy such as https://httpd.apache.org[Apache HTTP Server] or https://nginx.org[Nginx].
+
+[plantuml]
+----
+skinparam backgroundColor transparent
+skinparam monochrome true
+skinparam shadowing false
+skinparam padding 5
+
+scale 1.5
+
+rectangle client
+rectangle proxy
+rectangle "Jetty" as jetty1
+rectangle "Jetty" as jetty2
+
+client -- proxy
+proxy -- jetty1
+proxy -- jetty2
+----
+
+[WARNING]
+====
+HAProxy can communicate either HTTP/1.1 or HTTP/2 to backend servers such as Jetty.
+
+Apache HTTP Server and Nginx can only speak HTTP/1.1 to backend servers such as Jetty, and have no plans to support HTTP/2 towards backend servers.
+====
+
+In these setups, typically the proxy performs TLS offloading, and the communication with backend servers happens in clear-text.
+It is possible, however, to configure the proxy so that all the bytes arriving from the client are tunnelled opaquely to the backend Jetty server (that therefore needs to perform the TLS offloading) and viceversa the bytes arriving from the Jetty server are tunnelled opaquely to the client.
+
+Also in these setups, the TCP/IP connection terminating on the Jetty servers does not originate from the client, but from the proxy, so that the remote IP address and port number may be reported incorrectly in backend server logs, or worse applications may not work because they need to be able to differentiate different clients based on the client IP address.
+
+For this reason, intermediaries typically implement at least one of several _de facto_ standards to communicate information about the original client connection to the backend Jetty server.
+
+Jetty supports two methods to process client information sent by intermediaries:
+
+* The `Forwarded` HTTP header, defined in https://tools.ietf.org/html/rfc7239[RFC 7239] and replacing the old `X-Forwarded-*` headers, defined in <>.
+* The https://www.haproxy.org/download/2.2/doc/proxy-protocol.txt[Proxy Protocol], defined in <>.
+
+In both methods, web applications that call `HttpServletRequest.getRemoteAddr()` will receive the remote client IP address as specified by the client information sent by the intermediary, not the physical IP address of TCP connection with the intermediary.
+Likewise, `HttpServletRequest.getRemotePort()` will return the remote client IP port as specified by the client information sent by the intermediary, and `HttpServletRequest.isSecure()` will return whether the client made a secure request using the `https` scheme as specified by the client information sent by the intermediary.
+
+[[proxy-forwarded]]
+=== Configuring the Forwarded Header
+
+The `Forwarded` HTTP header is added by the intermediary with information about the client and the client request, for example:
+
+----
+GET / HTTP/1.1
+Host: domain.com
+Forwarded: for=2.36.72.144:21216;proto=https
+----
+
+In the example above, the intermediary added the `Forwarded` header specifying that the client remote address is `2.36.72.144:21216` and that the request was made with the `https` scheme.
+
+Let's assume you have already configured Jetty with the HTTP/1.1 protocol with the following command (issued from within the `$JETTY_BASE` directory):
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=http
+----
+
+Support for the `Forwarded` HTTP header (and its predecessor `X-Forwarded-*` headers) is enabled with the `http-forwarded` Jetty module:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=http-forwarded
+----
+
+[jetty%nowrap]
+....
+[jetty]
+setupArgs=--add-module=http
+args=--add-module=http-forwarded
+....
+
+With the `http-forwarded` Jetty module enabled, Jetty interprets the `Forwarded` header and makes its information available to web applications via the standard Servlet APIs.
+
+For further information about configuring the `http-forwarded` Jetty module, see xref:modules/standard.adoc#http-forwarded[this section].
+
+[[proxy-protocol]]
+=== Configuring the Proxy Protocol
+
+The https://www.haproxy.org/download/2.2/doc/proxy-protocol.txt[Proxy Protocol] is the _de facto_ standard, introduced by https://haproxy.org[HAProxy], to communicate client information to backend servers via the TCP connection, rather than via HTTP headers.
+
+The information about the client connection is sent as a small data frame on each newly established connection.
+This mechanism is therefore independent of any protocol, so it can be used for TLS, HTTP/1.1, HTTP/2, etc.
+
+[NOTE]
+====
+There are 2 versions of the proxy protocol: v1 and v2, both supported by Jetty.
+
+Proxy protocol v1 is human readable, but it only carries information about the client TCP connection (IP address and IP port).
+
+Proxy protocol v2 has a binary format, carries the information about the client TCP connection, and can carry additional arbitrary information encoded in pairs `(type, value)` where `type` is a single byte that indicates the value's meaning, and `value` is a variable length byte array that can encode user-defined data.
+====
+
+Support for the proxy protocol can be enabled for the clear-text connector or for the secure connector (or both).
+
+Let's assume you have already configured Jetty with the HTTP/1.1 clear-text protocol with the following command (issued from within the `$JETTY_BASE` directory):
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=http
+----
+
+To enable proxy protocol support for the clear-text connector, enable the `proxy-protocol` Jetty module:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=proxy-protocol
+----
+
+[jetty%nowrap]
+....
+[jetty]
+setupArgs=--add-module=http
+args=--add-module=proxy-protocol
+....
+
+Starting Jetty yields:
+
+----
+$ java -jar $JETTY_HOME/start.jar
+----
+
+[jetty%nowrap]
+....
+[jetty]
+args=--module=proxy-protocol
+highlight=(\{.*:8080})
+....
+
+Note how in the example above the list of protocols for the clear-text connector is first `proxy` and then `http/1.1`.
+For every new TCP connection, Jetty first interprets the proxy protocol bytes with the client information; after this initial proxy protocol processing, Jetty interprets the incoming bytes as HTTP/1.1 bytes.
+
+Enabling proxy protocol support for the secure connector is similar.
+
+Let's assume you have already configured Jetty with the HTTP/1.1 secure protocol and the test KeyStore with the following command (issued from within the `$JETTY_BASE` directory):
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=https,test-keystore
+----
+
+Enable the `proxy-protocol-ssl` Jetty module with the following command (issued from within the `$JETTY_BASE` directory):
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-module=proxy-protocol-ssl
+----
+
+[jetty%nowrap]
+....
+[jetty]
+setupArgs=--add-module=https
+args=--add-module=proxy-protocol-ssl
+....
+
+Starting Jetty yields:
+
+----
+$ java -jar $JETTY_HOME/start.jar
+----
+
+[jetty%nowrap]
+....
+[jetty]
+setupArgs=--add-modules=https,test-keystore,proxy-protocol-ssl
+highlight=(\{.*:8443})
+....
+
+Note how in the example above the list of protocols for the secure connector is first `proxy`, then `ssl` and then `http/1.1`.
+
+[[proxy-haproxy]]
+=== HAProxy and Jetty with HTTP/1.1 and HTTP/2
+
+https://haproxy.org[HAProxy] is an open source solution that offers load balancing and proxying for TCP and HTTP based application, and can be used as a replacement for Apache or Nginx when these are used as reverse proxies.
+
+The deployment proposed here has HAProxy playing the role that Apache and Nginx usually do: to perform the TLS offloading (that is, decrypt incoming bytes and encrypt outgoing bytes) and then forwarding the now clear-text traffic to a backend Jetty server, speaking either HTTP/1.1 or HTTP/2.
+Since HAProxy's TLS offloading is based on OpenSSL, it is much more efficient than the Java implementation shipped with OpenJDK.
+
+After you have installed HAProxy on your system, you want to configure it so that it can perform TLS offloading.
+
+HAProxy will need a single file containing the X509 certificates and the private key, all in https://en.wikipedia.org/wiki/X.509[PEM format], with the following order:
+
+1. The site certificate; this certificate's Common Name refers to the site domain (for example: CN=*.webtide.com) and is signed by Certificate Authority #1.
+2. The Certificate Authority #1 certificate; this certificate may be signed by Certificate Authority #2.
+3. The Certificate Authority #2 certificate; this certificate may be signed by Certificate Authority #3; and so on until the Root Certificate Authority.
+4. The Root Certificate Authority certificate.
+5. The private key corresponding to the site certificate.
+
+Refer to the xref:keystore/index.adoc[section about KeyStores] for more information about generating the required certificates and private key.
+
+Now you can create the HAProxy configuration file (in Linux it's typically `/etc/haproxy/haproxy.cfg`).
+This is a minimal configuration:
+
+.haproxy.cfg
+[source,subs=verbatim]
+----
+global
+tune.ssl.default-dh-param 1024
+
+defaults
+timeout connect 10000ms
+timeout client 60000ms
+timeout server 60000ms
+
+frontend fe_http <1>
+mode http
+bind *:80
+# Redirect to https
+redirect scheme https code 301
+
+frontend fe_https <2>
+mode tcp
+bind *:443 ssl no-sslv3 crt /path/to/domain.pem ciphers TLSv1.2 alpn h2,http/1.1
+default_backend be_http
+
+backend be_http <3>
+mode tcp
+server domain 127.0.0.1:8282 send-proxy-v2
+----
+<1> The `fe_http` front-end accepts connections on port 80 and redirects them to use the `https` scheme.
+<2> The `fe_https` front-end accepts connections on port 443, and it is where the TLS decryption/encryption happens.
+You must specify the path to the PEM file containing the TLS key material (the `crt /path/to/domain.pem` part), the ciphers that are suitable for HTTP/2 (`ciphers TLSv1.2`), and the ALPN protocols supported (`alpn h2,http/1.1`).
+This front-end then forwards the now decrypted bytes to the backend in `mode tcp`.
+The `mode tcp` says that HAProxy will not try to interpret the bytes but instead opaquely forwards them to the backend.
+<3> The `be_http` backend will forward (again in `mode tcp`) the clear-text bytes to a Jetty connector that talks clear-text HTTP/2 and HTTP/1.1 on port 8282.
+The `send-proxy-v2` directive sends the proxy protocol v2 bytes to the backend server.
+
+On the Jetty side, you need to enable the following modules:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=proxy-protocol,http2c,http,deploy
+----
+
+You need to specify the host (`127.0.0.1`) and port (`8282`) you have configured in HAProxy when you start Jetty:
+
+----
+$ java -jar $JETTY_HOME/start.jar jetty.http.host=127.0.0.1 jetty.http.port=8282
+----
+
+[NOTE]
+====
+You want the Jetty connector that listens on port `8282` to be available only to HAProxy, and not to remote clients.
+
+For this reason, you want to specify the `jetty.http.host` property on the command line (or in `$JETTY_BASE/start.d/http.ini` to make this setting persistent) to bind the Jetty connector only on the loopback interface (`127.0.0.1`), making it available to HAProxy but not to remote clients.
+
+If your Jetty instance runs on a different machine and/or on a different (sub)network, you may want to adjust both the back-end section of the HAProxy configuration file and the `jetty.http.host` property to match accordingly.
+====
+
+With this configuration for HAProxy and Jetty, browsers supporting HTTP/2 will connect to HAProxy, which will decrypt the traffic and send it to Jetty.
+Likewise, HTTP/1.1 clients will connect to HAProxy, which will decrypt the traffic and send it to Jetty.
+
+The Jetty connector, configured with the `http2c` and the `http` modules is able to distinguish whether the incoming bytes are HTTP/2 or HTTP/1.1 and will handle the request accordingly.
+
+The response is relayed back to HAProxy, which will encrypt it and send it back to the remote client.
+
+This configuration offers you efficient TLS offloading, HTTP/2 support and transparent fallback to HTTP/1.1 for clients that don't support HTTP/1.1.
+
+[[websocket]]
+== WebSocket
+
+WebSocket is a network protocol for bidirectional data communication initiated via the https://tools.ietf.org/html/rfc7230#section-6.7[HTTP/1.1 upgrade mechanism].
+WebSocket provides a simple, low-level, framing protocol layered over TCP.
+One or more WebSocket frames compose a WebSocket _message_ that is either a UTF-8 _text_ message or _binary_ message.
+
+Jetty provides an implementation of the following standards and specifications.
+
+http://tools.ietf.org/html/rfc6455[RFC-6455] - The WebSocket Protocol::
+Jetty supports version 13 of the released and final specification.
+
+http://www.jcp.org/en/jsr/detail?id=356[JSR-356] - The Java WebSocket API (`javax.websocket`)::
+This is the official Java API for working with WebSockets.
+
+https://tools.ietf.org/html/rfc7692[RFC-7692] - WebSocket Per-Message Deflate Extension::
+This is the replacement for perframe-compression, switching the compression to being based on the entire message, not the individual frames.
+
+https://tools.ietf.org/html/rfc8441[RFC-8441] - Bootstrapping WebSockets with HTTP/2::
+Allows a single stream of an HTTP/2 connection to be upgraded to WebSocket.
+This allows one TCP connection to be shared by both protocols and extends HTTP/2's more efficient use of the network to WebSockets.
+
+[[websocket-configure]]
+=== Configuring WebSocket
+
+Jetty provides two WebSocket implementations: one based on the Java WebSocket APIs defined by JSR 356, provided by module `websocket-javax`, and one based on Jetty specific WebSocket APIs, provided by module `websocket-jetty`.
+The Jetty `websocket` module enables both implementations, but each implementation can be enabled independently.
+
+NOTE: Remember that a WebSocket connection is always initiated from the HTTP protocol (either an HTTP/1.1 upgrade or an HTTP/2 connect), therefore to enable WebSocket you need to enable HTTP.
+
+To enable WebSocket support, you also need to decide what version of the HTTP protocol you want WebSocket to be initiated from, and whether you want secure HTTP.
+
+For example, to enable clear-text WebSocket from HTTP/1.1, use the following command (issued from within the `$JETTY_BASE` directory):
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=http,websocket
+----
+
+To enable secure WebSocket from HTTP/2, use the following command (issued from within the `$JETTY_BASE` directory):
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=http2,websocket
+----
+
+When enabling secure protocols you need a valid KeyStore (read xref:keystore/index.adoc[this section] to create your own KeyStore).
+As a quick example, you can enable the xref:modules/standard.adoc#test-keystore[`test-keystore` module], that creates on-the-fly a KeyStore containing a self-signed certificate:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=test-keystore
+----
+
+To enable WebSocket on both HTTP/1.1 and HTTP/2, both clear-text and secure, use the following command (issued from within the `$JETTY_BASE` directory):
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=http,https,http2c,http2,websocket
+----
+
+[[websocket-disable]]
+=== Selectively Disabling WebSocket
+
+Enabling the WebSocket Jetty modules comes with a startup cost because Jetty must perform two steps:
+
+. Scan web applications `+*.war+` files (and all the jars and classes inside it) looking for WebSocket EndPoints classes (either annotated with WebSocket API annotations or extending/implementing WebSocket API classes/interfaces).
+This can be a significant cost if your web application contains a lot of classes and/or jar files.
+
+. Configure and wire WebSocket EndPoints so that WebSocket messages are delivered to the correspondent WebSocket EndPoint.
+
+WebSocket support is by default enabled for all web applications.
+
+For a specific web application, you can disable step 2 for Java WebSocket support (i.e. when the `websocket-javax` module is enabled) by setting the context attribute `org.eclipse.jetty.websocket.javax` to `false`:
+
+[,xml]
+----
+
+
+
+
+
+ org.eclipse.jetty.websocket.javax
+ false
+
+
+ ...
+
+
+----
+
+Furthermore, for a specific web application, you can disable step 1 (and therefore also step 2) as described in the xref:annotations/index.adoc[annotations processing section].
+
+[[websocket-webapp-client]]
+=== Using WebSocket Client in WebApps
+
+Web applications may need to use a WebSocket client to communicate with third party WebSocket services.
+
+If the web application uses the Java WebSocket APIs, the WebSocket client APIs are provided by the Servlet Container and are available to the web application by enabling the WebSocket server APIs, and therefore you must enable the `websocket-javax` Jetty module.
+
+However, the Java WebSocket Client APIs are quite limited (for example, they do not support secure WebSocket).
+For this reason, web applications may want to use the Jetty WebSocket Client APIs.
+
+When using the Jetty WebSocket Client APIs, web applications should include the required jars and their dependencies in the `WEB-INF/lib` directory of the `+*.war+` file.
+Alternatively, when deploying your web applications in Jetty, you can enable the `websocket-jetty-client` Jetty module to allow web applications to use the Jetty WebSocket Client APIs provided by Jetty, without the need to include jars and their dependencies in the `+*.war+` file.
diff --git a/documentation/jetty/modules/operations-guide/pages/quickstart/index.adoc b/documentation/jetty/modules/operations-guide/pages/quickstart/index.adoc
new file mode 100644
index 00000000000..e2e22c754b4
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/quickstart/index.adoc
@@ -0,0 +1,67 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= Faster Web Application Deployment
+
+The auto discovery features of the Servlet Specification can make deployments slow and uncertain.
+Auto discovery of web application configuration can be useful during the development as it allows new features and frameworks to be enabled simply by dropping in a jar file.
+However for production deployment, the need to scan the contents of many jars can have a significant impact at startup time.
+
+The `quickstart` module allows a webapp to be pre-scanned, making startup predictable and faster.
+During scanning all declarative configuration (ie from web.xml, web-fragment.xml and annotations) are encoded into an effective `web.xml`, called `WEB-INF/quickstart-web.xml`, which can be inspected to understand what will be deployed.
+
+[NOTE]
+====
+Programmatic configuration is _not_ encoded into the generated `quickstart-web.xml` file.
+====
+
+With `quickstart`, webapps that took many seconds to scan and deploy can now be deployed in a few hundred milliseconds.
+
+== Enabling
+
+Enable the `quickstart` module for your jetty base:
+
+----
+$ cd $JETTY-BASE
+$ java -jar $JETTY_HOME/start.jar --add-module=quickstart
+----
+
+The `$JETTY-BASE/start.d/quickstart.ini` file contains these configurable parameters:
+
+jetty.quickstart.mode::
+ The values are:
+
+ AUTO:::
+ Allows jetty to run either with or without a `quickstart-web.xml` file.
+ If jetty detects the file, then it will be used, otherwise the app is started normally.
+ GENERATE:::
+ In this mode, jetty will generate a `quickstart-web.xml` file and then terminate.
+ Use this mode first before changing to either `AUTO` or `QUICKSTART`.
+ QUICKSTART:::
+ In this mode, if jetty does not detect a `quickstart-web.xml` file then jetty will not start.
+
+jetty.quickstart.origin::
+Use this parameter to set the name of the attribute in the `quickstart-web.xml` file that contains the origin of each element.
+Knowing the descriptor or annotation from which each element derives can be useful for debugging.
+Note that the origin attribute does not conform to the web xml schema, so if you deploy with xml validation, you'll see errors.
+It is probably best to do a few trial runs with the attribute set, then turn it off for final generation.
+
+jetty.quickstart.xml::
+Use this parameter to change the name of the generated file.
+By default this is `quickstart-web.xml` in the webapp's `WEB-INF` directory.
+The file named by this parameter will always be interpreted relative to `WEB-INF`.
+
+If your webapp is a war file, you will need to either first unpack it yourself, or use a context xml file (or code equivalent) that calls `WebAppContext.setExtractWAR(true)`.
+If you allow Jetty to do the unpacking, it will use the usual mechanisms to find the location to which to unpack.
+Note that by default Jetty unpacks to a temporary location which is _not_ reused between executions.
+So either specify the directory to which to unpack, or make a `work` directory in your base to ensure the unpacked war is preserved and reused across restarts.
diff --git a/documentation/jetty/modules/operations-guide/pages/server/index.adoc b/documentation/jetty/modules/operations-guide/pages/server/index.adoc
new file mode 100644
index 00000000000..d25d0f8d319
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/server/index.adoc
@@ -0,0 +1,363 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= Jetty Server
+
+The Jetty `Server` object is the central component that links protocol connectors to web applications.
+
+The `Server` component is defined by the xref:modules/standard.adoc#server[`server` Jetty module], that in turn depends on other Jetty modules that provide key functionalities, in particular:
+
+* <>
+* xref:modules/standard.adoc#bytebufferpool[`ByteBuffer` pooling]
+* <>
+
+[[logging]]
+== Logging
+
+There are two types of logging that can be configured in Jetty:
+
+* The logging of Jetty itself, that logs the server activity
+* The HTTP request logging, that logs information about HTTP requests and responses processed by Jetty
+
+[[logging-server]]
+=== Server Logging
+
+The Jetty code uses the http://slf4j.org/[SLF4J] API for its logging.
+
+Thanks to the SLF4J library, the logging of the Jetty server can therefore be directed to the implementation (called SLF4J _binding_) of your choice.
+
+The Jetty project provides an SLF4J binding (via the `jetty-slf4j-impl` Maven artifact) that is used as the default SLF4J binding.
+
+The logging of the Jetty server itself is enabled by default with the `logging` Jetty module, which is a transitive dependency of the `server` module and therefore it is typically always enabled.
+
+The `logging` Jetty module is a _virtual_ module (see xref:modules/index.adoc#names[this section]) and its default implementation is provided by the `logging-jetty` Jetty module, which uses the Jetty SLF4J binding.
+
+[[logging-server-default]]
+==== Default Configuration
+
+The Jetty SLF4J binding is configured with an appender (`org.eclipse.jetty.logging.StdErrAppender`) that directs the logging to `System.err`, and reads its configuration from a file named `jetty-logging.properties` that must be found in the class-path.
+
+The `StdErrAppender` format is:
+
+----
+::::
+----
+
+where `=yyyy-MM-dd HH:mm:ss.SSS`.
+
+You can configure `StdErrAppender` by specifying the following properties in `jetty-logging.properties`:
+
+org.eclipse.jetty.logging.appender.NAME_CONDENSE=::
+Specifies whether to condense logger names, so that for example `org.eclipse.jetty.util.QueuedThreadPool` becomes `oeju.QueuedThreadPool`.
+Default value is `true`.
+
+org.eclipse.jetty.logging.appender.MESSAGE_ALIGN=::
+Specifies the column at which the logging `` should be printed.
+The value `0` specifies no alignment.
+Default value is `0`.
+
+org.eclipse.jetty.logging.appender.MESSAGE_ESCAPE=::
+Specifies whether to escape ISO control characters such as `\r` or `\n` present in the message.
+Character `\r` is replaced with `<` and character `\n` is replaced with `|`; all other ISO control characters are replaced with `?`.
+Default value is `false`.
+
+org.eclipse.jetty.logging.appender.ZONE_ID=::
+Specifies the timezone ID (such as `PST`, or `America/Los_Angeles` or `GMT-8:00`) for the `` part of the logging line.
+The empty string specifies the `UTC` timezone.
+Default value is the local timezone.
+
+The `logging-jetty` Jetty module, enabled transitively, provides the configuration file `$JETTY_BASE/resources/jetty-logging.properties` to configure the logging levels, for example:
+
+----
+$ cd $JETTY_BASE
+$ java -jar $JETTY_HOME/start.jar --add-modules=http
+----
+
+----
+$JETTY_BASE
+├── resources
+│ └── jetty-logging.properties
+└── start.d
+ └── http.ini
+----
+
+.jetty-logging.properties
+[,properties]
+----
+# Do not condense logger names.
+org.eclipse.jetty.logging.appender.NAME_CONDENSE=false
+
+# By default, log at INFO level all Jetty loggers.
+org.eclipse.jetty.LEVEL=INFO
+
+# However, the Jetty client loggers log at DEBUG level.
+org.eclipse.jetty.client.LEVEL=DEBUG
+----
+
+The logging levels that you can specify in the `jetty-logging.properties` file are the usual SLF4J logging levels, `TRACE`, `DEBUG`, `INFO`, `WARN` and `ERROR`, plus two additional levels:
+
+* `ALL`, which is an alias for `TRACE`
+* `OFF`, which disables entirely the logging (not even `ERROR` level messages are logged)
+
+When using the Jetty SLF4J binding, the logging levels can be dynamically changed via JMX, see xref:troubleshooting/index.adoc#logging[the troubleshooting section] for more information.
+
+[[logging-server-default-rolling]]
+==== Capturing Logs to a Rolling File
+
+Having the logging output on `System.err` may be fine at development time, but you typically want the logs to be captured in a file so that they can be looked at even if you don't have a terminal (for example, you started Jetty as a service).
+
+The `console-capture` Jetty module allows you to capture what is written to `System.out` and `System.err` and write it to a log file, by default under the `$JETTY_BASE/logs/` directory.
+
+The `console-capture` Jetty module defines a number of properties that you can customize to control the log directory, the number of days rolled files are retained, etc.
+See the xref:modules/standard.adoc#console-capture[`console-capture` module] for more information.
+
+[NOTE]
+====
+The `console-capture` Jetty module should be used only in conjunction with the `logging-jetty` module, as other SLF4J bindings such as LogBack or Log4j2 have their own, more sophisticated, rolling file appenders.
+====
+
+[[logging-server-custom]]
+==== Custom Configuration
+
+You can use a different SLF4J binding if you are more familiar with other logging libraries, or if you need custom logging appenders.
+There are a number of out-of-the-box Jetty modules that you can use:
+
+* `logging-logback`, to use the http://logback.qos.ch/[LogBack] binding
+* `logging-log4j2`, to use the https://logging.apache.org/log4j/2.x/[Log4j2] binding
+* `logging-log4j1`, to use the https://logging.apache.org/log4j/1.2/[Log4j1] binding (note that Log4j 1.x is end-of-life)
+* `logging-jul`, to use the `java.util.logging` binding
+* `logging-noop`, to use the SLF4J no-operation binding (discards all logging)
+
+[[logging-server-custom-logback]]
+==== Logging with LogBack
+
+You can enable, for example, the `logging-logback` Jetty module in this way (from the `$JETTY_BASE` directory):
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=logging-logback,http
+----
+
+Since LogBack is released under a license that is different from Jetty's, you will be prompted to accept the LogBack license.
+Once you accept the LogBack license, you will have the following directory structure:
+
+----
+$JETTY_BASE
+├── lib
+│ └── logging
+│ ├── logback-classic-.jar
+│ └── logback-core-.jar
+├── resources
+│ └── logback.xml
+└── start.d
+ ├── http.ini
+ └── logging-logback.ini
+----
+
+As you can see, the Jetty module system downloaded the required LogBack `+*.jar+` files, and created a `$JETTY_BASE/resources/logback.xml` file that you can configure to customize your LogBack logging.
+Please refer to the http://logback.qos.ch/manual/configuration.html[LogBack configuration manual] for more information about how to configure LogBack.
+
+[[logging-server-custom-log4j2]]
+==== Logging with Log4j2
+
+Similarly to <>, you can enable the `logging-log4j2` Jetty module in this way (from the `$JETTY_BASE` directory):
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=logging-log4j2,http
+----
+
+After accepting the Log4j2 license, you will have the following directory structure:
+
+----
+$JETTY_BASE
+├── lib
+│ └── logging
+│ ├── log4j-api-.jar
+│ ├── log4j-core-.jar
+│ └── log4j-slf4j18-impl-.jar
+├── resources
+│ └── log4j2.xml
+└── start.d
+ ├── http.ini
+ └── logging-log4j2.ini
+----
+
+The Jetty module system downloaded the required Log4j2 `+*.jar+` files, and created a `$JETTY_BASE/resources/log4j2.xml` file that you can configure to customize your Log4j2 logging.
+
+[[logging-server-bridges]]
+==== Bridging Logging to SLF4J
+
+When you use libraries that provide the features you need (for example, JDBC drivers), it may be possible that those libraries use a different logging framework than SLF4J.
+
+SLF4J provides http://www.slf4j.org/legacy.html[bridges for legacy logging APIs] that allows you to bridge logging from one of these legacy logging frameworks to SLF4J.
+Once the logging is bridged to SLF4J, you can use the <> or the <> so that your logging is centralized in one place only.
+
+Jetty provides out-of-the-box modules that you can enable to bridge logging from other logging frameworks to SLF4J.
+
+[[logging-server-bridge-jul]]
+==== Bridging `java.util.logging`
+
+For libraries that use `java.util.logging` as their logging framework you can enable the `logging-jul-capture` Jetty module.
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=logging-jul-capture
+----
+
+The `logging-jul-capture` Jetty module implies `--exec` and therefore spawns a second JVM (see xref:start/index.adoc#start[this section]) because it needs to provide the system property `java.util.logging.config.file` (so that `java.util.logging` can read the configuration from the specified file), and because it needs to make available on the System ClassLoader the class `org.slf4j.bridge.SLF4JBridgeHandler`.
+
+For example, a library that uses `java.util.logging` as its logging library is the Postgresql JDBC driver.
+With the `logging-jul-capture` Jetty module, the logging follows this diagram:
+
+[plantuml]
+----
+skinparam backgroundColor transparent
+skinparam monochrome true
+skinparam shadowing false
+
+participant "Postgresql JDBC" as postgresql
+participant java.util.logging
+participant SLF4JBridgeHandler
+participant Jetty
+participant SLF4J
+participant "Jetty SLF4J Binding" as binding
+
+
+postgresql -> java.util.logging
+java.util.logging -> SLF4JBridgeHandler
+SLF4JBridgeHandler -> SLF4J
+SLF4J -> binding
+Jetty -> SLF4J
+SLF4J -> binding
+----
+
+Note how Jetty logs directly to SLF4J, while the Postgresql JDBC driver logs to SLF4J through the `SLF4JBridgeHandler`.
+They both arrive to the SLF4J binding, in this case the Jetty SLF4J binding (but could be any other SLF4J binding such as LogBack).
+
+[[og-logging-request]]
+=== Request Logging
+
+HTTP requests and responses can be logged to provide data that can be later analyzed with other tools, that can provide information such as the most frequently accessed request URIs, the response status codes, the request/response content lengths, geographical information about the clients, etc.
+
+Request logging is enabled by enabling the `requestlog` Jetty module.
+In the example below, both the `http` Jetty module and the `requestlog` module are enabled, so that you can make HTTP requests to the server and have them logged:
+
+----
+$ cd $JETTY_BASE
+$ java -jar $JETTY_HOME/start.jar --add-modules=http,requestlog
+----
+
+The `$JETTY_BASE` directory looks like this:
+
+[source]
+----
+$JETTY_BASE
+├── logs
+├── resources
+│ └── jetty-logging.properties
+└── start.d
+ ├── http.ini
+ └── requestlog.ini
+----
+
+The `$JETTY_BASE/start.d/requestlog.ini` file is the Jetty module configuration file that allows you to configure the `requestlog` module, see xref:modules/standard.adoc#requestlog[this section] for more details.
+
+By default the `requestlog` Jetty module produces the `$JETTY_BASE/logs/yyyy_MM_dd.request.log`, where the pattern `yyyy_MM_dd` is replaced with the current date, for example `2020_01_31`.
+
+The format of the request log lines is the result of a _format string_ that uses formatting symbols to log relevant request/response data.
+
+The default format is the https://en.wikipedia.org/wiki/Common_Log_Format[NCSA Format] extended with referrer data and user-agent data.
+A typical log line looks like this:
+
+[,options=nowrap]
+----
+192.168.0.100 - - [31/Jan/2020:20:30:40 +0000] "GET / HTTP/1.1" 200 6789 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36"
+----
+
+The line above (that uses fake values) shows `192.168.0.100` for the client IP address, a hard-coded `-` for the identity, `-` for the authenticated user name, `[31/Jan/2020:20:30:40 +0000]` for the date and time with timezone, `"GET / HTTP/1.1"` for the HTTP request line, `200` for the HTTP response status code, `6789` for the HTTP response content length, `"-"` for the referrer and `"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36"` for the user-agent.
+
+The format string can be customized as described in xref:modules/standard.adoc#requestlog[this section].
+Request log files are rolled every day, and retained for customizable number of days, by default 90 days.
+
+[NOTE]
+====
+When Jetty is behind a load balancer, you want to log the remote client IP address, not the load balancer IP address. Refer to xref:protocols/index.adoc#proxy[this section] to configure the load balancer and Jetty to retain the remote client IP address information.
+====
+
+[[threadpool]]
+== Thread Pooling
+
+Jetty uses thread pooling to efficiently execute tasks that provide Jetty functionalities.
+
+Like any other component, the Jetty thread pool is configured and enabled via the xref:modules/standard.adoc#threadpool[`threadpool` Jetty module], that is transitively enabled by the xref:modules/standard.adoc#server[`server` Jetty module] which, in turn, is transitively enabled by a protocol module such as the xref:protocols/index.adoc#http[`http` Jetty module]:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=http
+----
+
+The command above gives you the default configuration for the thread pool.
+
+If you want to explicitly configure the thread pool, it is enough to explicitly specify the xref:modules/standard.adoc#threadpool[`threadpool`] module:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=threadpool,http
+----
+
+After the command above, the `$JETTY_BASE` directory looks like this:
+
+[source,subs=verbatim]
+----
+$JETTY_BASE
+├── resources
+│ └── jetty-logging.properties
+└── start.d
+ ├── http.ini
+ └── threadpool.ini
+----
+
+Now you can customize the `threadpool.ini` file to explicitly configure the thread pool.
+
+[[threadpool-virtual]]
+=== Virtual Threads Support
+
+Virtual threads have been introduced as a preview feature in Java 19 and Java 20, and have become an official feature since Java 21.
+
+The xref:modules/standard.adoc#threadpool-virtual-preview[`threadpool-virtual-preview`] Jetty module provides support for virtual threads in Java 19 and Java 20, and it is mutually exclusive with the `threadpool` Jetty module.
+
+The xref:modules/standard.adoc#threadpool-virtual[`threadpool-virtual`] Jetty module provides support for virtual threads in Java 21 or later, and it is mutually exclusive with the `threadpool` Jetty module.
+
+If you have already enabled the `threadpool` Jetty module, it is sufficient to remove it by removing the `$JETTY_BASE/start.d/threadpool.ini` file.
+
+When using Java 21 or later, you can enable the xref:modules/standard.adoc#threadpool-virtual[`threadpool-virtual`] module:
+
+----
+$ java -jar $JETTY_HOME/start.jar --add-modules=threadpool-virtual,http
+----
+
+After the command above, the `$JETTY_BASE` directory looks like this:
+
+[source,subs=verbatim]
+----
+$JETTY_BASE
+├── resources
+│ └── jetty-logging.properties
+└── start.d
+ ├── http.ini
+ └── threadpool-virtual.ini
+----
+
+Now you can customize the `threadpool-virtual.ini` file to explicitly configure the thread pool and the virtual threads and then start Jetty:
+
+[jetty%nowrap]
+....
+[jetty]
+setupArgs=--add-modules=threadpool-virtual,http
+....
diff --git a/documentation/jetty/modules/operations-guide/pages/session/index.adoc b/documentation/jetty/modules/operations-guide/pages/session/index.adoc
new file mode 100644
index 00000000000..c8d9ba3aa36
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/session/index.adoc
@@ -0,0 +1,974 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+= HTTP Session Management
+
+HTTP sessions are a concept within the Servlet API which allow requests to store and retrieve information across the time a user spends in an application.
+Jetty offers a number of pluggable alternatives for managing and distributing/persisting sessions.
+Choosing the best alternative is an important consideration for every application as is the correct configuration to achieve optimum performance.
+
+[[overview]]
+== HTTP Session Overview
+
+=== Terminology
+
+Before diving into the specifics of how to plug-in and configure various alternative HTTP session management modules, let's review some useful terminology:
+
+Session::
+is a means of retaining information across requests for a particular user.
+The Servlet Specification defines the semantics of sessions.
+Some of the most important characteristics of sessions is that they have a unique id and that their contents cannot be shared between different contexts (although the id can be): if a session is invalidated in one context, then all other sessions that share the same id in other contexts will also be invalidated.
+Sessions can expire or they can be explicitly invalidated.
+
+SessionIdManager::
+is responsible for allocating session ids.
+A Jetty server can have at most 1 SessionIdManager.
+
+HouseKeeper::
+is responsible for periodically orchestrating the removal of expired sessions.
+This process is referred to as <>.
+
+SessionHandler::
+is responsible for managing the lifecycle of sessions.
+A context can have at most 1 `SessionHandler`.
+
+SessionCache::
+is a L1 cache of in-use session objects.
+The `SessionCache` is used by the `SessionHandler`.
+
+SessionDataStore::
+is responsible for all clustering/persistence operations on sessions.
+A `SessionCache` uses a `SessionDataStore` as a backing store.
+
+CachingSessionDataStore::
+is an L2 cache of session data.
+A `SessionCache` can use a `CachingSessionDataStore` as its backing store.
+
+More details on these concepts can be found in the xref:programming-guide:server/session.adoc[Programming Guide].
+
+[NOTE]
+====
+``SessionDataStore``s implementations interact with other, usually third party, systems responsible for storing and/or distributing session information.
+Sessions can be distributed without being persisted.
+They can also be persisted without being distributed.
+Because persisting session information to a shared store is a very common way of distributing (also known as "clustering") sessions, in the documentation we will often refer to just "persisting".
+====
+
+[[modules]]
+=== Session Modules
+
+There are a number of modules that offer pluggable alternatives for http session management.
+You can design how you want to cache and store http sessions by selecting alternative combinations of session modules.
+
+For example, Jetty ships with two alternative implementations of the `SessionCache`:
+
+* one that caches sessions in memory: <>
+* one that does not actually cache: <>
+
+There are at least 6 alternative implementations of the `SessionDataStore` that you can use to persist/distribute your http sessions:
+
+* file system storage: <>
+* relational database storage: <>
+* NoSQL database storage: <>
+* Google Cloud datastore storage: <>
+* Hazelcast: <> or <>
+* Infinispan: <> or <>
+
+TIP: It is worth noting that if you do not configure _any_ session modules, Jetty will still provide HTTP sessions that are cached in memory but are never persisted.
+
+[[base]]
+== The Base Session Module
+
+The `sessions` module is the base module that all other session modules depend upon.
+As such it will be _transitively_ enabled if you enable any of the other session modules: you need to _explicitly_ enable it if you wish to _change_ any settings from their defaults.
+
+Enabling the `sessions` module puts the `$JETTY_HOME/etc/sessions/id-manager.xml` file onto the execution path and generates a `$JETTY_BASE/start.d/sessions.ini` file.
+
+The `id-manager.xml` file instantiates a `DefaultSessionIdManager` and `HouseKeeper`.
+The former is used to generate and manage session ids whilst the latter is responsible for periodic <> of expired sessions.
+
+=== Configuration
+
+The `$JETTY_BASE/start.d/sessions.ini` file contains these configuration properties:
+
+jetty.sessionIdManager.workerName::
+This uniquely identifies the jetty server instance and is applied to the `SessionIdManager`.
+You can either provide a value for this property, or you can allow Jetty to try and synthesize a `workerName` - the latter option is _only_ advisable in the case of a single, non-clustered deployment.
+There are two ways a default `workerName` can be synthesized:
+
+* if running on Google AppEngine, the `workerName` will be formed by concatenating the values of the environment variables `JETTY_WORKER_INSTANCE` and `GAE_MODULE_INSTANCE`
+* otherwise, the `workerName` will be formed by concatenating the environment variable `JETTY_WORKER_INSTANCE` and the literal `0`.
+
+So, if you're not running on Google AppEngine, and you haven't configured one, the workerName will always be: `node0`.
+
+IMPORTANT: If you have more than one Jetty instance, it is *crucial* that you configure the `workerName` differently for each instance.
+
+jetty.sessionScavengeInterval.seconds::
+This is the period in _seconds_ between runs of the `HouseKeeper`, responsible for orchestrating the removal of expired sessions.
+By default it will run approximately every 600 secs (ie 10 mins).
+As a rule of thumb, you should ensure that the <> interval is shorter than the `