Merge remote-tracking branch 'origin/jetty-12.1.x' into jetty-12.1.x-servletUpgrade

This commit is contained in:
Lachlan Roberts 2024-08-27 17:57:45 +10:00
commit 594a65099c
No known key found for this signature in database
GPG Key ID: 5663FB7A8FF7E348
620 changed files with 8482 additions and 2708 deletions

View File

@ -1,5 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Jetty Security Reports
url: https://eclipse.dev/jetty/security_reports.php
url: https://jetty.org/security.html
about: Please raise security issues here.

View File

@ -18,7 +18,7 @@ labels: Bug
**OS type/version**
**Description**
<!-- Do not report security issues here! See [Jetty Security Reports](https://eclipse.dev/jetty/security_reports.php) -->
<!-- Do not report security issues here! See [Jetty Security Reports](https://jetty.org/security.html) -->
**How to reproduce?**

View File

@ -56,7 +56,7 @@ This release process will produce releases:
- [ ] Merge release branches back to main branches and delete release branches.
- [ ] Verify release existence in Maven Central by triggering the Jenkins builds of CometD.
- [ ] Update Jetty versions on the website ( follow instructions in [jetty-website](https://github.com/eclipse/jetty-website/blob/master/README.md) ).
+ [ ] Update (or check) [Download](https://eclipse.dev/jetty/download.php) page is updated.
+ [ ] Update (or check) [Download](https://jetty.org/download.html) page is updated.
+ [ ] Update (or check) documentation page(s) are updated.
- [ ] Publish GitHub Releases.
- [ ] Prepare release announcement for mailing lists.

View File

@ -13,12 +13,34 @@
= Eclipse Jetty {page-version}
This section of the site contains the documentation for {page-component-title} {page-version}.
This is the main documentation page for the Eclipse Jetty Project.
Jetty provides a highly scalable and memory-efficient web server and Servlet container, supporting web protocols such as HTTP/1.1, HTTP/2, HTTP/3 and WebSocket.
Furthermore, Jetty offers integrations with many other technologies, such as OSGi, JMX, JNDI, JAAS, CDI, etc. and with the relevant Jakarta EE technologies.
Jetty is open source and are freely available for commercial use and distribution under either the link:https://www.eclipse.org/legal/epl-2.0/[Eclipse Public License v2] or the link:https://www.apache.org/licenses/LICENSE-2.0[Apache License v2].
Jetty can either be used as a standalone server to deploy web applications, or as a library that can be used in your code as a dependency.
.Jetty Versions and Compatibilities
[cols="1a,1a,1a,1a", options="header"]
|===
| Jetty Version | Required Java Version | Jakarta EE Version | Status
| Jetty 12.1.x | Java 17 | Jakarta EE11, EE10, EE9, EE8 | Development
| Jetty 12.0.x | Java 17 | Jakarta EE10, EE9, EE8 | Stable
| Jetty 11.0.x | Java 11 | Jakarta EE9 | EOL (see link:https://github.com/jetty/jetty.project/issues/10485[#10485])
| Jetty 10.0.x | Java 11 | Jakarta EE8 | EOL (see link:https://github.com/jetty/jetty.project/issues/10485[#10485])
| Jetty 9.4.x | Java 8 | Jakarta EE7 | EOL (see link:https://github.com/jetty/jetty.project/issues/7958[#7958])
|===
== 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.
The Operations Guide targets sysops, devops, and developers who want to install 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.
The Programming Guide targets developers who want to use the Jetty libraries in their applications, and advanced sysops/devops that want to customize the deployment of web applications.

View File

@ -26,6 +26,10 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-ethereum</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-infinispan-embedded-query</artifactId>
@ -54,6 +58,10 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-session</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-slf4j-impl</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-unixdomain-server</artifactId>

View File

@ -17,17 +17,29 @@ import java.util.concurrent.Executors;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.VirtualThreadPool;
@SuppressWarnings("unused")
public class ArchitectureDocs
{
public void configureVirtualThreads()
public void queuedVirtualThreads()
{
// tag::virtual[]
// tag::queuedVirtual[]
QueuedThreadPool threadPool = new QueuedThreadPool();
threadPool.setVirtualThreadsExecutor(Executors.newVirtualThreadPerTaskExecutor());
Server server = new Server(threadPool);
// end::virtual[]
// end::queuedVirtual[]
}
public void virtualVirtualThreads()
{
// tag::virtualVirtual[]
VirtualThreadPool threadPool = new VirtualThreadPool();
// Limit the max number of current virtual threads.
threadPool.setMaxThreads(200);
Server server = new Server(threadPool);
// end::virtualVirtual[]
}
}

View File

@ -329,11 +329,11 @@ public class ContentDocs
// Read a chunk.
chunk = source.read();
// No chunk, demand to be called back when there will be more chunks.
// If no chunk, schedule a demand callback when there are more chunks.
if (chunk == null)
{
source.demand(this::iterate);
return Action.IDLE;
source.demand(this::succeeded);
return Action.SCHEDULED;
}
// The read failed, re-throw the failure
@ -341,7 +341,7 @@ public class ContentDocs
if (Content.Chunk.isFailure(chunk))
throw chunk.getFailure();
// Copy the chunk.
// Copy the chunk by scheduling an asynchronous write.
sink.write(chunk.isLast(), chunk.getByteBuffer(), this);
return Action.SCHEDULED;
}
@ -349,8 +349,9 @@ public class ContentDocs
@Override
protected void onSuccess()
{
// After every successful write, release the chunk.
chunk.release();
// After every successful write, release the chunk
// and reset to the next chunk
chunk = Content.Chunk.releaseAndNext(chunk);
}
@Override
@ -360,15 +361,21 @@ public class ContentDocs
callback.succeeded();
}
@Override
protected void onFailure(Throwable cause)
{
// The copy is failed, fail the callback.
// This method is invoked before a write() has completed, so
// the chunk is not released here, but in onCompleteFailure().
callback.failed(cause);
}
@Override
protected void onCompleteFailure(Throwable failure)
{
// In case of a failure, either on the
// read or on the write, release the chunk.
chunk.release();
// The copy is failed, fail the callback.
callback.failed(failure);
// In case of a failure, this method is invoked when the write()
// is completed, and it is now possible to release the chunk.
chunk = Content.Chunk.releaseAndNext(chunk);
}
@Override

View File

@ -24,9 +24,12 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.ConnectionStatistics;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.Retainable;
import org.eclipse.jetty.io.RetainableByteBuffer;
import org.eclipse.jetty.io.SelectorManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
@ -225,11 +228,13 @@ public class SelectorManagerDocs
// tag::echo-correct[]
class EchoConnection extends AbstractConnection
{
private final ByteBufferPool.Sized pool;
private final IteratingCallback callback = new EchoIteratingCallback();
public EchoConnection(EndPoint endp, Executor executor)
public EchoConnection(EndPoint endp, ByteBufferPool.Sized pool, Executor executor)
{
super(endp, executor);
this.pool = pool;
}
@Override
@ -250,20 +255,20 @@ public class SelectorManagerDocs
class EchoIteratingCallback extends IteratingCallback
{
private ByteBuffer buffer;
private RetainableByteBuffer buffer;
@Override
protected Action process() throws Throwable
{
// Obtain a buffer if we don't already have one.
if (buffer == null)
buffer = BufferUtil.allocate(1024);
buffer = pool.acquire();
int filled = getEndPoint().fill(buffer);
int filled = getEndPoint().fill(buffer.getByteBuffer());
if (filled > 0)
{
// We have filled some bytes, echo them back.
getEndPoint().write(this, buffer);
getEndPoint().write(this, buffer.getByteBuffer());
// Signal that the iteration should resume
// when the write() operation is completed.
@ -273,14 +278,15 @@ public class SelectorManagerDocs
{
// We don't need the buffer anymore, so
// don't keep it around while we are idle.
buffer = null;
buffer = Retainable.release(buffer);
// No more bytes to read, declare
// again interest for fill events.
fillInterested();
fillInterested(this);
// Signal that the iteration is now IDLE.
return Action.IDLE;
// Signal that the iteration is now SCHEDULED
// for a fillable callback.
return Action.SCHEDULED;
}
else
{
@ -291,17 +297,11 @@ public class SelectorManagerDocs
}
@Override
protected void onCompleteSuccess()
protected void onCompleted(Throwable cause)
{
// The iteration completed successfully.
getEndPoint().close();
}
@Override
protected void onCompleteFailure(Throwable cause)
{
// The iteration completed with a failure.
// The iteration completed.
getEndPoint().close(cause);
buffer = Retainable.release(buffer);
}
@Override

View File

@ -523,14 +523,16 @@ public class WebSocketDocs
@Override
public void succeed()
{
// When the send succeeds, succeed this IteratingCallback.
// Map the o.e.j.websocket.api.Callback to o.e.j.util.Callback.
// When the send() succeeds, succeed this IteratingCallback.
succeeded();
}
@Override
public void fail(Throwable x)
{
// When the send fails, fail this IteratingCallback.
// Map the o.e.j.websocket.api.Callback to o.e.j.util.Callback.
// When the send() fails, fail this IteratingCallback.
failed(x);
}

View File

@ -17,6 +17,7 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.SocketAddress;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.file.Path;
@ -34,6 +35,7 @@ import org.eclipse.jetty.client.BasicAuthentication;
import org.eclipse.jetty.client.BufferingResponseListener;
import org.eclipse.jetty.client.BytesRequestContent;
import org.eclipse.jetty.client.CompletableResponseListener;
import org.eclipse.jetty.client.Connection;
import org.eclipse.jetty.client.ConnectionPool;
import org.eclipse.jetty.client.ContentResponse;
import org.eclipse.jetty.client.Destination;
@ -72,6 +74,7 @@ import org.eclipse.jetty.http3.client.transport.HttpClientTransportOverHTTP3;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.Transport;
import org.eclipse.jetty.io.ssl.SslHandshakeListener;
import org.eclipse.jetty.quic.client.ClientQuicConfiguration;
@ -1180,4 +1183,29 @@ public class HTTPClientDocs
.send();
// end::mixedTransports[]
}
public void connectionInformation() throws Exception
{
// tag::connectionInformation[]
HttpClient httpClient = new HttpClient();
httpClient.start();
ContentResponse response = httpClient.newRequest("http://domain.com/path")
// The connection information is only available starting from the request begin event.
.onRequestBegin(request ->
{
Connection connection = request.getConnection();
// Obtain the address of the server.
SocketAddress remoteAddress = connection.getRemoteSocketAddress();
System.getLogger("connection").log(INFO, "Server address: %s", remoteAddress);
// Obtain the SslSessionData.
EndPoint.SslSessionData sslSessionData = connection.getSslSessionData();
if (sslSessionData != null)
System.getLogger("connection").log(INFO, "SslSessionData: %s", sslSessionData);
})
.send();
// end::connectionInformation[]
}
}

View File

@ -0,0 +1,58 @@
//
// ========================================================================
// 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.security.siwe.example;
import java.io.PrintWriter;
import java.nio.file.Paths;
import java.util.Objects;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.security.AuthenticationState;
import org.eclipse.jetty.security.Constraint;
import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.security.siwe.EthereumAuthenticator;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.session.SessionHandler;
import org.eclipse.jetty.util.Callback;
public class SignInWithEthereum
{
public static SecurityHandler createSecurityHandler(Handler handler)
{
// tag::configureSecurityHandler[]
// This uses jetty-core, but you can configure a ConstraintSecurityHandler for use with EE10.
SecurityHandler.PathMapped securityHandler = new SecurityHandler.PathMapped();
securityHandler.setHandler(handler);
securityHandler.put("/*", Constraint.ANY_USER);
// Add the EthereumAuthenticator to the securityHandler.
EthereumAuthenticator authenticator = new EthereumAuthenticator();
securityHandler.setAuthenticator(authenticator);
// In embedded you can configure via EthereumAuthenticator APIs.
authenticator.setLoginPath("/login.html");
// Or you can configure with parameters on the SecurityHandler.
securityHandler.setParameter(EthereumAuthenticator.LOGIN_PATH_PARAM, "/login.html");
// end::configureSecurityHandler[]
return securityHandler;
}
}

View File

@ -0,0 +1,117 @@
//
// ========================================================================
// 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.security.siwe;
import java.io.PrintWriter;
import java.nio.file.Paths;
import java.util.Objects;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.security.AuthenticationState;
import org.eclipse.jetty.security.Constraint;
import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.security.siwe.EthereumAuthenticator;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.session.SessionHandler;
import org.eclipse.jetty.util.Callback;
public class SignInWithEthereumEmbeddedExample
{
public static void main(String[] args) throws Exception
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(8080);
server.addConnector(connector);
String resourcePath = Paths.get(Objects.requireNonNull(SignInWithEthereumEmbeddedExample.class.getClassLoader().getResource("")).toURI())
.resolve("../../src/main/resources/")
.normalize().toString();
System.err.println(resourcePath);
ResourceHandler resourceHandler = new ResourceHandler();
resourceHandler.setDirAllowed(false);
resourceHandler.setBaseResourceAsString(resourcePath);
Handler.Abstract handler = new Handler.Wrapper(resourceHandler)
{
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
String pathInContext = Request.getPathInContext(request);
if ("/login.html".equals(pathInContext))
{
return super.handle(request, response, callback);
}
else if ("/logout".equals(pathInContext))
{
AuthenticationState.logout(request, response);
Response.sendRedirect(request, response, callback, "/");
callback.succeeded();
return true;
}
AuthenticationState authState = Objects.requireNonNull(AuthenticationState.getAuthenticationState(request));
response.getHeaders().add(HttpHeader.CONTENT_TYPE, "text/html");
try (PrintWriter writer = new PrintWriter(Content.Sink.asOutputStream(response)))
{
writer.write("UserPrincipal: " + authState.getUserPrincipal());
writer.write("<br><a href=\"/logout\">Logout</a>");
}
callback.succeeded();
return true;
}
};
SecurityHandler securityHandler = createSecurityHandler(handler);
SessionHandler sessionHandler = new SessionHandler();
sessionHandler.setHandler(securityHandler);
ContextHandler contextHandler = new ContextHandler();
contextHandler.setContextPath("/");
contextHandler.setHandler(sessionHandler);
server.setHandler(contextHandler);
server.start();
server.join();
}
public static SecurityHandler createSecurityHandler(Handler handler)
{
// tag::configureSecurityHandler[]
// This uses jetty-core, but you can configure a ConstraintSecurityHandler for use with EE10.
SecurityHandler.PathMapped securityHandler = new SecurityHandler.PathMapped();
securityHandler.setHandler(handler);
securityHandler.put("/*", Constraint.ANY_USER);
// Add the EthereumAuthenticator to the securityHandler.
EthereumAuthenticator authenticator = new EthereumAuthenticator();
securityHandler.setAuthenticator(authenticator);
// In embedded you can configure via EthereumAuthenticator APIs.
authenticator.setLoginPath("/login.html");
// Or you can configure with parameters on the SecurityHandler.
securityHandler.setParameter(EthereumAuthenticator.LOGIN_PATH_PARAM, "/login.html");
// end::configureSecurityHandler[]
return securityHandler;
}
}

View File

@ -176,6 +176,7 @@ public class ServerDocs
@Override
public void onFillable()
{
// Called from fillInterested() in onOpen() to start iteration.
callback.iterate();
}
@ -206,11 +207,8 @@ public class ServerDocs
// the application completed the request processing.
return Action.SCHEDULED;
}
else
{
// Did not receive enough JSON bytes,
// loop around to try to read more.
}
// Did not receive enough JSON bytes to complete the
// JSON parsing, loop around to try to read more bytes.
}
else if (filled == 0)
{
@ -218,12 +216,11 @@ public class ServerDocs
// don't keep it around while we are idle.
buffer = null;
// No more bytes to read, declare
// again interest for fill events.
fillInterested();
// No more bytes to read, declare again interest for fill events.
fillInterested(this);
// Signal that the iteration is now IDLE.
return Action.IDLE;
// Signal that the iteration is now SCHEDULED for fill interest callback.
return Action.SCHEDULED;
}
else
{

View File

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sign-In with Ethereum</title>
<script src="https://cdn.jsdelivr.net/npm/web3@1.6.1/dist/web3.min.js"></script>
</head>
<body>
<h4>Sign-In with Ethereum</h4>
<button id="siwe">Sign-In with Ethereum</button>
<form id="loginForm" action="/auth/login" method="POST" style="display: none;">
<input type="hidden" id="signatureField" name="signature">
<input type="hidden" id="messageField" name="message">
</form>
<p class="alert" style="display: none;">Result: <span id="siweResult"></span></p>
<script>
let provider = window.ethereum;
let accounts;
if (!provider) {
document.getElementById('siweResult').innerText = 'MetaMask is not installed. Please install MetaMask to use this feature.';
} else {
document.getElementById('siwe').addEventListener('click', async () => {
try {
// Request account access if needed
accounts = await provider.request({ method: 'eth_requestAccounts' });
const domain = window.location.host;
const from = accounts[0];
// Fetch nonce from the server
const nonceResponse = await fetch('/auth/nonce');
const nonceData = await nonceResponse.json();
const nonce = nonceData.nonce;
const siweMessage = `${domain} wants you to sign in with your Ethereum account:\n${from}\n\nI accept the MetaMask Terms of Service: https://community.metamask.io/tos\n\nURI: https://${domain}\nVersion: 1\nChain ID: 1\nNonce: ${nonce}\nIssued At: ${new Date().toISOString()}`;
const signature = await provider.request({
method: 'personal_sign',
params: [siweMessage, from]
});
console.log("signature: " + signature)
console.log("nonce: " + nonce)
console.log("length: " + length)
document.getElementById('signatureField').value = signature;
document.getElementById('messageField').value = siweMessage;
document.getElementById('loginForm').submit();
} catch (error) {
console.error('Error during login:', error);
document.getElementById('siweResult').innerText = `Error: ${error.message}`;
document.getElementById('siweResult').parentElement.style.display = 'block';
}
});
}
</script>
</body>
</html>

View File

@ -708,6 +708,34 @@ If you want to use virtual threads, introduced as a preview feature in Java 19 a
See also the xref:server/index.adoc#threadpool[section about configuring the thread pool].
[[threadpool-all-virtual]]
== Module `threadpool-all-virtual`
The `threadpool-all-virtual` module allows you to configure the server-wide thread pool, similarly to what you can do with the <<threadpool,`threadpool`>> Jetty module, so that all threads are 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 <<threadpool-virtual-preview,`threadpool-virtual-preview`>> Jetty module instead.
The module properties to configure the thread pool are:
----
include::{jetty-home}/modules/threadpool-all-virtual.mod[tags=documentation]
----
The property `jetty.threadpool.maxThreads` limits, using a `Semaphore`, the number of current virtual threads in use.
Limiting the number of current virtual threads helps to limit resource usage in applications, especially in case of load spikes.
When an unlimited number of virtual threads is allowed, the server might be brought down due to resource (typically memory) exhaustion.
[CAUTION]
====
Even when using virtual threads, Jetty uses non-blocking I/O, and dedicates a thread to each `java.nio.channels.Selector` to perform the `Selector.select()` operation.
Currently (up to Java 22), calling `Selector.select()` from a virtual thread pins the carrier thread.
When using the `threadpool-all-virtual` Jetty module, if you have `N` selectors, then `N` carrier threads will be pinned by the virtual threads calling `Selector.select()`, possibly making your system less efficient, and at worst locking up the entire system if there are no carrier threads available to run virtual threads.
====
[[threadpool-virtual]]
== Module `threadpool-virtual`

View File

@ -328,32 +328,30 @@ Virtual threads have been introduced as a preview feature in Java 19 and Java 20
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.
When using Java 21, there are two Jetty modules available:
* xref:modules/standard.adoc#threadpool-virtual[`threadpool-virtual`]
* xref:modules/standard.adoc#threadpool-all-virtual[`threadpool-all-virtual`]
Both are 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:
The xref:modules/standard.adoc#threadpool-virtual[`threadpool-virtual`] Jetty module provides a mixed thread mode, where platform threads are used to run internal Jetty tasks, but application code is invoked using virtual threads.
The xref:modules/standard.adoc#threadpool-all-virtual[`threadpool-all-virtual`] Jetty module provides a thread mode where all threads are virtual threads, including those used internally by Jetty.
You can enable either module using:
----
$ java -jar $JETTY_HOME/start.jar --add-modules=threadpool-virtual,http
----
After the command above, the `$JETTY_BASE` directory looks like this:
or
[source]
----
$JETTY_BASE
├── resources
│ └── jetty-logging.properties
└── start.d
├── http.ini
└── threadpool-virtual.ini
$ java -jar $JETTY_HOME/start.jar --add-modules=threadpool-all-virtual,http
----
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
....
After the command above, the `$JETTY_BASE/start.d/` directory will contain the corresponding `threadpool-virtual.ini` or `threadpool-all-virtual.ini` file.
You can now explicitly configure the thread pool module properties inside the `+*.ini+` file and then start Jetty.

View File

@ -43,6 +43,8 @@
** xref:troubleshooting/state-tracking.adoc[]
** xref:troubleshooting/component-dump.adoc[]
** xref:troubleshooting/debugging.adoc[]
* Jetty Security
** xref:security/siwe-support.adoc[]
* Migration Guides
** xref:migration/94-to-10.adoc[]
** xref:migration/11-to-12.adoc[]

View File

@ -187,7 +187,7 @@ In turn, this calls `IteratingCallback.process()`, an abstract method that must
Method `process()` must return:
* `Action.SCHEDULED`, to indicate whether the loop has performed a non-blocking, possibly asynchronous, operation
* `Action.IDLE`, to indicate that the loop should temporarily be suspended to be resumed later
* `Action.IDLE`, to indicate that the loop should temporarily be suspended to be resumed later with another call to iterate
* `Action.SUCCEEDED` to indicate that the loop exited successfully
Any exception thrown within `process()` exits the loops with a failure.
@ -209,13 +209,18 @@ If this was the only active network connection, the system would now be idle, wi
Eventually, the Jetty I/O system will notify that the `write()` completed; this notifies the `IteratingCallback` that can now resume the loop and call `process()` again.
When `process()` is called, it is possible that zero bytes are read from the network; in this case, you want to deallocate the buffer since the other peer may never send more bytes for the `Connection` to read, or it may send them after a long pause -- in both cases we do not want to retain the memory allocated by the buffer; next, you want to call `fillInterested()` to declare again interest for read events, and return `Action.IDLE` since there is nothing to write back and therefore the loop may be suspended.
When more bytes are again available to be read from the network, `onFillable()` will be called again and that will start the iteration again.
When `process()` is called, it is possible that zero bytes are read from the network; in this case, you want to deallocate the buffer since the other peer may never send more bytes for the `Connection` to read, or it may send them after a long pause -- in both cases we do not want to retain the memory allocated by the buffer; next, you want to call `fillInterested(this)` to declare again interest for read events, and return `Action.SCHEDULED` since a callback is scheduled to occur once filling is possible.
Another possibility is that during `process()` the read returns `-1` indicating that the other peer has closed the connection; this means that there will not be more bytes to read and the loop can be exited, so you return `Action.SUCCEEDED`; `IteratingCallback` will then call `onCompleteSuccess()` where you can close the `EndPoint`.
The last case is that during `process()` an exception is thrown, for example by `EndPoint.fill(ByteBuffer)` or, in more advanced implementations, by code that parses the bytes that have been read and finds them unacceptable; any exception thrown within `process()` will be caught by `IteratingCallback` that will exit the loop with a failure and call `onCompleteFailure(Throwable)` with the exception that has been thrown, where you can close the `EndPoint`, passing the exception that is the reason for closing prematurely the `EndPoint`.
Note that some failures may occur whilst a scheduled operation is in progress.
Such failures are notified immediately via the `onFailure(Throwable)` method, but care must be taken to not release any resources that may still be in use by the scheduled operation.
The `onCompleteFailure(Throwable)` method is called when both a failure has occurred and any scheduled operation has completed.
An example of this issue is that a buffer used for a write operation cannot be returned to a pool in `onFailure(Throwable)` as the write may still be progressing.
Either the buffer must be removed from the pool in `onFailure(Throwable)` or the release of the buffer deferred until `onCompleteFailure(Throwable)` is called.
[IMPORTANT]
====
Asynchronous programming is hard.
@ -356,9 +361,9 @@ You must initiate a second write only when the first is finished, for example:
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/ContentDocs.java[tags=sinkMany]
----
When you need to perform an unknown number of writes, you must use an `IteratingCallback`, explained in <<echo,this section>>, to avoid ``StackOverFlowError``s.
When you need to perform an unknown number of writes, you may use an `IteratingCallback`, explained in <<echo,this section>>, to avoid ``StackOverFlowError``s.
For example, to copy from a `Content.Source` to a `Content.Sink` you should use the convenience method `Content.copy(Content.Source, Content.Sink, Callback)`.
For example, to copy from a `Content.Source` to a `Content.Sink` you could use the convenience method `Content.copy(Content.Source, Content.Sink, Callback)`.
For illustrative purposes, below you can find the implementation of `copy(Content.Source, Content.Sink, Callback)` that uses an `IteratingCallback`:
[,java,indent=0]

View File

@ -235,11 +235,14 @@ Virtual threads have been introduced in Java 19 and Java 20 as a preview feature
NOTE: In Java versions where virtual threads are a preview feature, remember to add `+--enable-preview+` to the JVM command line options to use virtual threads.
[[thread-pool-virtual-threads-queued]]
==== Virtual Threads Support with `QueuedThreadPool`
`QueuedThreadPool` can be configured to use virtual threads by specifying the virtual threads `Executor`:
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/ArchitectureDocs.java[tags=virtual]
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/ArchitectureDocs.java[tags=queuedVirtual]
----
[CAUTION]
@ -255,3 +258,17 @@ Enabling virtual threads in `QueuedThreadPool` will default the number of reserv
Defaulting the number of reserved threads to zero ensures that the <<execution-strategy-pec,Produce-Execute-Consume mode>> is always used, which means that virtual threads will always be used for blocking tasks.
====
[[thread-pool-virtual-threads-virtual]]
==== Virtual Threads Support with `VirtualThreadPool`
`VirtualThreadPool` is an alternative to `QueuedThreadPool` that creates only virtual threads (no platform threads).
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/ArchitectureDocs.java[tags=virtualVirtual]
----
Despite the name, `VirtualThreadPool` does not pool virtual threads, but allows you to impose a limit on the maximum number of current virtual threads, in order to limit resource consumption.
Furthermore, you can configure it to track virtual threads so that a xref:troubleshooting/component-dump.adoc[Jetty component dump] will show all virtual threads, including those that are unmounted.

View File

@ -235,7 +235,12 @@ A second request with the same origin sent _after_ the first request/response cy
A second request with the same origin sent _concurrently_ with the first request will likely cause the opening of a second connection, depending on the connection pool implementation.
The configuration parameter `HttpClient.maxConnectionsPerDestination` (see also the <<configuration,configuration section>>) controls the max number of connections that can be opened for a destination.
NOTE: If opening connections to a given origin takes a long time, then requests for that origin will queue up in the corresponding destination until the connections are established.
[NOTE]
====
If opening connections to a given origin takes a long time, then requests for that origin will queue up in the corresponding destination until the connections are established.
To save the time spent opening connections, you can xref:connection-pool-precreate-connections[pre-create connections].
====
Each connection can handle a limited number of concurrent requests.
For HTTP/1.1, this number is always `1`: there can only be one outstanding request for each connection.
@ -528,6 +533,28 @@ This is a fancy example of how to mix HTTP versions and low-level transports:
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=mixedTransports]
----
[[connection-information]]
=== Request Connection Information
In order to send a request, it is necessary to obtain a connection, as explained in the xref:request-processing[request processing section].
The HTTP/1.1 protocol may send only one request at a time on a single connection, while multiplexed protocols such as HTTP/2 may send many requests at a time on a single connection.
You can access the connection information, for example the local and remote `SocketAddress`, or the `SslSessionData` if the connection is secured, in the following way:
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=connectionInformation]
----
[NOTE]
====
The connection information is only available when the request is associated with a connection.
This means that the connection is not available in the _request queued_ event, but only starting from the _request begin_ event.
For more information about request events, see xref:non-blocking[this section].
====
[[configuration]]
== HttpClient Configuration

View File

@ -23,3 +23,24 @@ You may use the xref:client/index.adoc[Jetty client-side library] in your applic
Likewise, you may use the xref:server/index.adoc[Jetty server-side library] to quickly create an HTTP or REST service without having to create a web application archive file (a `+*.war+` file) and without having to deploy it to a Jetty standalone server that you would have to download and install.
This guide will walk you through the design of the Jetty libraries and how to use its classes to write your applications.
== Code Deprecation Policy
As the Jetty code evolves, classes and/or methods are deprecated using the `@Deprecated` annotation and will be removed in a future Jetty release.
The Jetty release numbering follows this scheme: `<major>.<minor>.<micro>`. For example, 12.0.5 has `major=12`, `minor=0` and `micro=5`.
As much as possible, deprecated code is not removed in micro releases.
Deprecated code may be removed in major releases.
Deprecated code may be removed in minor releases, but only if it has been deprecated for at least 6 micro releases.
For example, let's assume that Jetty 12.1.0 (a new minor release) is released after the release of Jetty 12.0.11.
Then, code that was deprecated in Jetty 12.0.5 or earlier may be removed from Jetty 12.1.0 (because it has been deprecated for more than 6 micro releases).
On the other hand, code that was deprecated in Jetty 12.0.8 may be removed in Jetty 12.1.3 (because it has been deprecated for 3 micro releases in Jetty 12.0.x, and for 3 micro releases in Jetty 12.1.x -- 12.1.0, 12.1.1 and 12.1.2).
[NOTE]
====
There could be rare cases where code (possibly not even deprecated) must be removed earlier than specified above to address security vulnerabilities.
====

View File

@ -0,0 +1,180 @@
//
// ========================================================================
// 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
// ========================================================================
//
[[siwe-support]]
= SIWE Support
== Introduction
Sign-In with Ethereum (SIWE) is a decentralized authentication protocol that allows users to authenticate using their Ethereum account.
This enables users to retain more control over their identity and provides an alternative to protocols such as OpenID Connect, which rely on a centralized identity provider.
Sign-In with Ethereum works by using off-chain services to sign a standard message format defined by EIP-4361 (https://eips.ethereum.org/EIPS/eip-4361). The user signs the SIWE message to prove ownership of the Ethereum address. This is verified by the server by extracting the Ethereum address from the signature and comparing it to the address supplied in the SIWE message.
Typically, you would rely on a browser extension such as MetaMask to provide a user-friendly way for users to sign the message with their Ethereum account.
=== Support
Currently Jetty only provides support SIWE in Jetty 12.1+ and only for `jetty-core`, and `ee10`+ environments. It is enabled by adding the `EtheremAuthenticator` to the `SecurityHandler` of your web application.
== Usage
=== Enabling SIWE
The Sign-In with Ethereum module can be enabled when using Standalone Jetty with.
[source,subs=attributes+]
----
$ java -jar $JETTY_HOME/start.jar --add-modules=siwe
----
If using embedded Jetty you must add the `EthereumAuthenticator` to your `SecurityHandler`.
=== Configuration
Configuration of the `EthereumAuthenticator` is done through init params on the `ServletContext` or `SecurityHandler`. The `loginPath` is the only mandatory configuration and the others have defaults that you may wish to configure.
Login Path::
* Init param: `org.eclipse.jetty.security.siwe.login_path`
* Description: Unauthenticated requests are redirected to a login page where they must sign a SIWE message and send it to the server. This path represents a page in the application that contains the SIWE login page.
Nonce Path::
* Init param: `org.eclipse.jetty.security.siwe.nonce_path`
* Description: Requests to this path will generate a random nonce string which is associated with the session. The nonce is used in the SIWE Message to avoid replay attacks. The path at which this nonce is served can be configured through the init parameter. The application does not need to implement their own nonce endpoint, they just configure this path and the Authenticator handles it. The default value for this is `/auth/nonce` if left un-configured.
Authentication Path::
* Init param: `org.eclipse.jetty.security.siwe.authentication_path`
* Description: The authentication path is where requests containing a signed SIWE message are sent in order to authenticate the user. The default value for this is `/auth/login`.
Max Message Size::
* Init Param: `org.eclipse.jetty.security.siwe.max_message_size`
* Description: This is the max size of the authentication message which can be read by the implementation. This limit defaults to `4 * 1024`. This is necessary because the complete request content is read into a string and then parsed.
Logout Redirect Path::
* Init Param: `org.eclipse.jetty.security.siwe.logout_redirect_path`
* Description: Where the request is redirected to after logout. If left un-configured no redirect will be done upon logout.
Error Path::
* Init Param: `org.eclipse.jetty.security.siwe.error_path`
* Description: Path where Authentication errors are sent, this may contain an optional query string. An error description is available on the error page through the request parameter `error_description_jetty`. If this configuration is not set Jetty will send a 403 Forbidden response upon authentication errors.
Dispatch::
* Init Param: `org.eclipse.jetty.security.siwe.dispatch`
* Description: If set to true a dispatch will be done instead of a redirect to the login page in the case of an unauthenticated request. This defaults to false.
Authenticate New Users::
* Init Param: `org.eclipse.jetty.security.siwe.authenticate_new_users`
* Description: This can be set to false if you have a nested `LoginService` and only want to authenticate users known by the `LoginService`. This defaults to `true` meaning that any user will be authenticated regardless if they are known by the nested `LoginService`.
Domains::
* Init Param: org.eclipse.jetty.security.siwe.domains
* Description: This list of allowed domains to be declared in the `domain` field of the SIWE Message. If left blank this will allow all domains.
Chain IDs::
* Init Param: org.eclipse.jetty.security.siwe.chainIds
* Description: This list of allowed Chain IDs to be declared in the `chain-id` field of the SIWE Message. If left blank this will allow all Chain IDs.
=== Nested LoginService
A nested `LoginService` may be used to assign roles to users of a known Ethereum Address. Or the nested `LoginService` may be combined with the setting `authenticateNewUsers == false` to only allow authentication of known users.
For example a `HashLoginService` may be configured through the `jetty-ee10-web.xml` file:
[, xml, indent=0]
----
<Configure id="wac" class="org.eclipse.jetty.ee10.webapp.WebAppContext">
<Call id="ResourceFactory" class="org.eclipse.jetty.util.resource.ResourceFactory" name="of">
<Arg><Ref refid="Server"/></Arg>
<Call id="realmResource" name="newResource">
<Arg><SystemProperty name="jetty.base" default="."/>/etc/realm.properties</Arg>
</Call>
</Call>
<Call name="getSecurityHandler">
<Set name="loginService">
<New class="org.eclipse.jetty.security.HashLoginService">
<Set name="name">myRealm</Set>
<Set name="config"><Ref refid="realmResource"/></Set>
</New>
</Set>
</Call>
</Configure>
----
=== Application Implementation
EIP-4361 specifies the format of a SIWE Message, the overview of the Sign-In with Ethereum process, and message validation. However, it does not specify certain things like how the SIWE Message and signature are sent to the server for validation, and it does not specify the process the client acquires the nonce from the server. For this reason the `EthereumAuthenticator` has been made extensible to allow different implementations.
Currently Jetty supports authentication requests of type `application/x-www-form-urlencoded` or `multipart/form-data`, which contains the fields `message` and `signature`. Where `message` contains the full SIWE message, and `signature` is the ERC-1271 signature of the SIWE message.
The nonce endpoint provided by the `EthereumAuthenticator` returns a response with `application/json` format, with a single key of `nonce`.
=== Configuring Security Handler
[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/siwe/SignInWithEthereumEmbeddedExample.java[tags=configureSecurityHandler]
----
=== Login Page Example
Include the `Web3.js` library to interact with the users Ethereum wallet.
[,html,indent=0]
----
<script src="https://cdn.jsdelivr.net/npm/web3@1.6.1/dist/web3.min.js"></script>
----
HTML form to submit the sign in request.
[,html,indent=0]
----
<button id="siwe">Sign-In with Ethereum</button>
<form id="loginForm" action="/auth/login" method="POST" style="display: none;">
<input type="hidden" id="signatureField" name="signature">
<input type="hidden" id="messageField" name="message">
</form>
<p class="alert" style="display: none;">Result: <span id="siweResult"></span></p>
----
Add script to generate and sign the SIWE message when the sign-in button is pressed.
[,html,indent=0]
----
<script>
let provider = window.ethereum;
let accounts;
if (!provider) {
document.getElementById('siweResult').innerText = 'MetaMask is not installed. Please install MetaMask to use this feature.';
} else {
document.getElementById('siwe').addEventListener('click', async () => {
try {
accounts = await provider.request({ method: 'eth_requestAccounts' });
const domain = window.location.host;
const from = accounts[0];
// Fetch nonce from the server.
const nonceResponse = await fetch('/auth/nonce');
const nonceData = await nonceResponse.json();
const nonce = nonceData.nonce;
const siweMessage = `${domain} wants you to sign in with your Ethereum account:\n${from}\n\nI accept the MetaMask Terms of Service: https://community.metamask.io/tos\n\nURI: https://${domain}\nVersion: 1\nChain ID: 1\nNonce: ${nonce}\nIssued At: ${new Date().toISOString()}`;
document.getElementById('signatureField').value = await provider.request({
method: 'personal_sign',
params: [siweMessage, from]
});
document.getElementById('messageField').value = siweMessage;
document.getElementById('loginForm').submit();
} catch (error) {
console.error('Error during login:', error);
document.getElementById('siweResult').innerText = `Error: ${error.message}`;
document.getElementById('siweResult').parentElement.style.display = 'block';
}
});
}
</script>
----

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_10_0.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure id="sslConnector" class="org.eclipse.jetty.server.ServerConnector">

View File

@ -55,6 +55,11 @@
<artifactId>jetty-deploy</artifactId>
<version>12.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-ethereum</artifactId>
<version>12.0.13-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-http</artifactId>

View File

@ -1,8 +1,12 @@
# DO NOT EDIT THIS FILE - See: https://eclipse.dev/jetty/documentation/
# DO NOT EDIT THIS FILE - See: https://jetty.org/docs/
[description]
Adds the Jetty HTTP client to the server classpath.
Adds the Jetty HTTP client dependencies to the server classpath.
[tags]
client
[lib]
lib/jetty-client-${jetty.version}.jar
lib/jetty-alpn-client-${jetty.version}.jar
lib/jetty-alpn-java-client-${jetty.version}.jar
lib/jetty-client-${jetty.version}.jar

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.client;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.time.Duration;
import java.util.Map;
@ -73,10 +72,4 @@ public abstract class AbstractConnectorHttpClientTransport extends AbstractHttpC
context.put(ClientConnector.CLIENT_CONNECTOR_CONTEXT_KEY, connector);
destination.getOrigin().getTransport().connect(address, context);
}
@Override
public void connect(InetSocketAddress address, Map<String, Object> context)
{
connect((SocketAddress)address, context);
}
}

View File

@ -16,6 +16,7 @@ package org.eclipse.jetty.client;
import java.io.Closeable;
import java.net.SocketAddress;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Promise;
/**
@ -63,4 +64,13 @@ public interface Connection extends Closeable
{
return null;
}
/**
* @return the {@link EndPoint.SslSessionData} associated with
* the connection, or {@code null} if the connection is not secure.
*/
default EndPoint.SslSessionData getSslSessionData()
{
return null;
}
}

View File

@ -1,121 +0,0 @@
//
// ========================================================================
// 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.client;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jetty.client.internal.HttpContentResponse;
/**
* A {@link BufferingResponseListener} that is also a {@link Future}, to allow applications
* to block (indefinitely or for a timeout) until {@link #onComplete(Result)} is called,
* or to {@link #cancel(boolean) abort} the request/response conversation.
* <p>
* Typical usage is:
* <pre>
* Request request = httpClient.newRequest(...)...;
* FutureResponseListener listener = new FutureResponseListener(request);
* request.send(listener); // Asynchronous send
* ContentResponse response = listener.get(5, TimeUnit.SECONDS); // Timed block
* </pre>
*
* @deprecated Use {@link CompletableResponseListener} instead
*/
@Deprecated
public class FutureResponseListener extends BufferingResponseListener implements Future<ContentResponse>
{
private final AtomicBoolean cancelled = new AtomicBoolean();
private final CountDownLatch latch = new CountDownLatch(1);
private final Request request;
private ContentResponse response;
private Throwable failure;
public FutureResponseListener(Request request)
{
this(request, 2 * 1024 * 1024);
}
public FutureResponseListener(Request request, int maxLength)
{
super(maxLength);
this.request = request;
}
public Request getRequest()
{
return request;
}
@Override
public void onComplete(Result result)
{
response = new HttpContentResponse(result.getResponse(), getContent(), getMediaType(), getEncoding());
failure = result.getFailure();
latch.countDown();
}
@Override
public boolean cancel(boolean mayInterruptIfRunning)
{
if (cancelled.compareAndSet(false, true))
{
request.abort(new CancellationException());
return true;
}
return false;
}
@Override
public boolean isCancelled()
{
return cancelled.get();
}
@Override
public boolean isDone()
{
return latch.getCount() == 0 || isCancelled();
}
@Override
public ContentResponse get() throws InterruptedException, ExecutionException
{
latch.await();
return getResult();
}
@Override
public ContentResponse get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
{
boolean expired = !latch.await(timeout, unit);
if (expired)
throw new TimeoutException();
return getResult();
}
private ContentResponse getResult() throws ExecutionException
{
if (isCancelled())
throw (CancellationException)new CancellationException().initCause(failure);
if (failure != null)
throw new ExecutionException(failure);
return response;
}
}

View File

@ -471,13 +471,7 @@ public class HttpClient extends ContainerLifeCycle implements AutoCloseable
port = normalizePort(scheme, port);
Transport transport = request.getTransport();
if (transport == null)
{
// Ask the ClientConnector for backwards compatibility
// until ClientConnector.Configurator is removed.
transport = connector.newTransport();
if (transport == null)
transport = Transport.TCP_IP;
}
transport = Transport.TCP_IP;
return new Origin(scheme, new Origin.Address(host, port), request.getTag(), protocol, transport);
}

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.client;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Map;
@ -71,24 +70,8 @@ public interface HttpClientTransport extends ClientConnectionFactory
*
* @param address the address to connect to
* @param context the context information to establish the connection
* @deprecated use {@link #connect(SocketAddress, Map)} instead.
*/
@Deprecated
public void connect(InetSocketAddress address, Map<String, Object> context);
/**
* Establishes a physical connection to the given {@code address}.
*
* @param address the address to connect to
* @param context the context information to establish the connection
*/
public default void connect(SocketAddress address, Map<String, Object> context)
{
if (address instanceof InetSocketAddress)
connect((InetSocketAddress)address, context);
else
throw new UnsupportedOperationException("Unsupported SocketAddress " + address);
}
public void connect(SocketAddress address, Map<String, Object> context);
/**
* @return the factory for ConnectionPool instances

View File

@ -346,6 +346,12 @@ public class HttpProxy extends ProxyConfiguration.Proxy
return connection.getRemoteSocketAddress();
}
@Override
public EndPoint.SslSessionData getSslSessionData()
{
return connection.getSslSessionData();
}
@Override
public void send(Request request, Response.CompleteListener listener)
{

View File

@ -15,7 +15,6 @@ package org.eclipse.jetty.client.transport;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -91,19 +90,6 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
this(new ClientConnector(), HttpClientConnectionFactory.HTTP11);
}
/**
* <p>Creates a dynamic transport that speaks the given protocols, in order of preference
* (first the most preferred).</p>
*
* @param infos the protocols this dynamic transport speaks
* @deprecated use {@link #HttpClientTransportDynamic(ClientConnector, ClientConnectionFactory.Info...)}
*/
@Deprecated(since = "12.0.7", forRemoval = true)
public HttpClientTransportDynamic(ClientConnectionFactory.Info... infos)
{
this(findClientConnector(infos), infos);
}
/**
* <p>Creates a dynamic transport with the given {@link ClientConnector} and the given protocols,
* in order of preference (first the most preferred).</p>
@ -121,14 +107,6 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
);
}
private static ClientConnector findClientConnector(ClientConnectionFactory.Info[] infos)
{
return Arrays.stream(infos)
.flatMap(info -> info.getContainedBeans(ClientConnector.class).stream())
.findFirst()
.orElseGet(ClientConnector::new);
}
@Override
public Origin newOrigin(Request request)
{
@ -216,11 +194,7 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
Transport transport = request.getTransport();
if (transport == null)
{
// Ask the ClientConnector for backwards compatibility
// until ClientConnector.Configurator is removed.
transport = getClientConnector().newTransport();
if (transport == null)
transport = matchingInfos.get(0).newTransport();
transport = matchingInfos.get(0).newTransport();
request.transport(transport);
}

View File

@ -70,18 +70,6 @@ public class HttpDestination extends ContainerLifeCycle implements Destination,
private boolean stale;
private long activeNanoTime;
/**
* @param client the {@link HttpClient}
* @param origin the {@link Origin}
* @param intrinsicallySecure whether the destination is intrinsically secure
* @deprecated use {@link #HttpDestination(HttpClient, Origin)} instead
*/
@Deprecated(since = "12.0.7", forRemoval = true)
public HttpDestination(HttpClient client, Origin origin, boolean intrinsicallySecure)
{
this(client, origin);
}
/**
* <p>Creates a new HTTP destination.</p>
*

View File

@ -617,14 +617,8 @@ public abstract class HttpSender
}
@Override
protected void onCompleteFailure(Throwable x)
protected void onFailure(Throwable x)
{
if (chunk != null)
{
chunk.release();
chunk = Content.Chunk.next(chunk);
}
failRequest(x);
internalAbort(exchange, x);
@ -633,6 +627,14 @@ public abstract class HttpSender
promise.succeeded(true);
}
@Override
protected void onCompleteFailure(Throwable x)
{
if (chunk != null)
chunk.release();
chunk = Content.Chunk.next(chunk);
}
@Override
public InvocationType getInvocationType()
{

View File

@ -116,6 +116,12 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements IConne
return delegate.getRemoteSocketAddress();
}
@Override
public EndPoint.SslSessionData getSslSessionData()
{
return delegate.getSslSessionData();
}
@Override
public long getBytesIn()
{
@ -350,6 +356,12 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements IConne
return getEndPoint().getRemoteSocketAddress();
}
@Override
public EndPoint.SslSessionData getSslSessionData()
{
return getEndPoint().getSslSessionData();
}
@Override
public SendFailure send(HttpExchange exchange)
{

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.Retainable;
import org.eclipse.jetty.io.RetainableByteBuffer;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
@ -237,7 +238,9 @@ public class HttpSenderOverHTTP extends HttpSender
@Override
protected void onSuccess()
{
release();
headerBuffer = Retainable.release(headerBuffer);
chunkBuffer = Retainable.release(chunkBuffer);
contentByteBuffer = null;
}
@Override
@ -248,21 +251,16 @@ public class HttpSenderOverHTTP extends HttpSender
}
@Override
protected void onCompleteFailure(Throwable cause)
protected void onFailure(Throwable cause)
{
super.onCompleteFailure(cause);
release();
callback.failed(cause);
}
private void release()
@Override
protected void onCompleteFailure(Throwable cause)
{
if (headerBuffer != null)
headerBuffer.release();
headerBuffer = null;
if (chunkBuffer != null)
chunkBuffer.release();
chunkBuffer = null;
headerBuffer = Retainable.release(headerBuffer);
chunkBuffer = Retainable.release(chunkBuffer);
contentByteBuffer = null;
}
}
@ -334,11 +332,16 @@ public class HttpSenderOverHTTP extends HttpSender
}
}
@Override
protected void onFailure(Throwable cause)
{
callback.failed(cause);
}
@Override
protected void onCompleteFailure(Throwable cause)
{
release();
callback.failed(cause);
}
private void release()

View File

@ -1,4 +1,4 @@
<?xml version="1.0"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_10_0.dtd">
<?xml version="1.0"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<!-- =============================================================== -->
<!-- Attach the "core" environment app deployment provider -->

View File

@ -1,5 +1,5 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_10_0.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<!-- =============================================================== -->
<!-- Create the deployment manager -->

View File

@ -385,6 +385,10 @@ public class ContextProvider extends ScanningAppProvider
// Handle a context XML file
if (FileID.isXml(path))
{
ClassLoader coreContextClassLoader = Environment.CORE.equals(environment) ? findCoreContextClassLoader(path) : null;
if (coreContextClassLoader != null)
Thread.currentThread().setContextClassLoader(coreContextClassLoader);
context = applyXml(context, path, env, properties);
// Look for the contextHandler itself
@ -401,13 +405,11 @@ public class ContextProvider extends ScanningAppProvider
throw new IllegalStateException("Unknown context type of " + context);
// Set the classloader if we have a coreContextClassLoader
ClassLoader coreContextClassLoader = Environment.CORE.equals(environment) ? findCoreContextClassLoader(path) : null;
if (coreContextClassLoader != null)
contextHandler.setClassLoader(coreContextClassLoader);
return contextHandler;
}
// Otherwise it must be a directory or an archive
else if (!Files.isDirectory(path) && !FileID.isWebArchive(path))
{

View File

@ -12,7 +12,7 @@
<!-- // ======================================================================== -->
<!-- // -->
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_10_0.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure class="org.eclipse.jetty.server.handler.ContextHandler">
<Set name="contextPath">/global</Set>
<Call name="setAttribute">

View File

@ -1,5 +1,5 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_10_0.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call name="addBean">

View File

@ -1,5 +1,5 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_9_3.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_9_3.dtd">
<!-- ============================================================= -->
<!-- Configure the Jetty Server instance with an ID "Server" -->

View File

@ -1,9 +1,9 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_9_3.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_9_3.dtd">
<!-- =============================================================== -->
<!-- Documentation of this file format can be found at: -->
<!-- https://eclipse.dev/jetty/documentation/ -->
<!-- https://jetty.org/docs/ -->
<!-- -->
<!-- Additional configuration files are available in $JETTY_HOME/etc -->
<!-- and can be mixed in. See start.ini file for the default -->

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_9_3.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_9_3.dtd">
<Configure class="org.eclipse.jetty.ee9.webapp.WebAppContext">
<Set name="contextPath">/badapp</Set>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_10_0.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure class="org.eclipse.jetty.server.handler.ContextHandler">
<Set name="contextPath">/bar</Set>
<Set name="baseResourceAsString"><Property name="test.bar.resourceBase.alt" /></Set>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_10_0.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure class="org.eclipse.jetty.server.handler.ContextHandler">
<Set name="contextPath">/bar</Set>
<Set name="baseResourceAsString"><Property name="test.bar.resourceBase" /></Set>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_10_0.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure class="org.eclipse.jetty.ee9.webapp.WebAppContext">
<Set name="contextPath">/foo</Set>
<Set name="war">

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_10_0.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure class="org.eclipse.jetty.server.handler.ContextHandler">
<Set name="contextPath">/simple</Set>
<Set name="tempDirectory"><Property name="test.tmpBase" /></Set>

View File

@ -1,4 +1,4 @@
# DO NOT EDIT THIS FILE - See: https://eclipse.dev/jetty/documentation/
# DO NOT EDIT THIS FILE - See: https://jetty.org/docs/
[description]
# tag::description[]

View File

@ -101,6 +101,12 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
return delegate.getRemoteSocketAddress();
}
@Override
public EndPoint.SslSessionData getSslSessionData()
{
return delegate.getSslSessionData();
}
protected Flusher getFlusher()
{
return flusher;
@ -359,6 +365,12 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
return getEndPoint().getRemoteSocketAddress();
}
@Override
public EndPoint.SslSessionData getSslSessionData()
{
return getEndPoint().getSslSessionData();
}
@Override
public SendFailure send(HttpExchange exchange)
{

View File

@ -15,6 +15,7 @@ package org.eclipse.jetty.fcgi.generator;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
@ -104,43 +105,60 @@ public class Flusher
protected void onSuccess()
{
if (active != null)
{
active.release();
active.succeeded();
active = null;
active = null;
}
}
@Override
public void onCompleteFailure(Throwable x)
public void onFailure(Throwable cause)
{
if (active != null)
active.failed(x);
active = null;
while (true)
active.failed(cause);
List<Entry> entries;
try (AutoLock ignored = lock.lock())
{
Entry entry = poll();
if (entry == null)
break;
entry.failed(x);
entries = new ArrayList<>(queue);
}
entries.forEach(entry -> entry.failed(cause));
}
@Override
protected void onCompleteFailure(Throwable cause)
{
if (active != null)
{
active.release();
active = null;
}
List<Entry> entries;
try (AutoLock ignored = lock.lock())
{
entries = new ArrayList<>(queue);
queue.clear();
}
entries.forEach(Entry::release);
}
}
private record Entry(ByteBufferPool.Accumulator accumulator, Callback callback) implements Callback
private record Entry(ByteBufferPool.Accumulator accumulator, Callback callback)
{
@Override
public void succeeded()
{
if (accumulator != null)
accumulator.release();
callback.succeeded();
}
@Override
public void failed(Throwable x)
{
callback.failed(x);
}
private void release()
{
if (accumulator != null)
accumulator.release();
callback.failed(x);
}
}
}

View File

@ -259,8 +259,7 @@ public class ServerFCGIConnection extends AbstractMetaDataConnection implements
boolean released = inputBuffer.release();
if (LOG.isDebugEnabled())
LOG.debug("releaseInputBuffer {} {}", released, this);
if (released)
inputBuffer = null;
inputBuffer = null;
}
private int fillInputBuffer()

View File

@ -0,0 +1,16 @@
[description]
Adds the Jetty HTTP/2 client transport dependencies to the server classpath.
[tags]
client
http2
[depends]
client
http2-client
[files]
maven://org.eclipse.jetty.http2/jetty-http2-client-transport/${jetty.version}/jar|lib/http2/jetty-http2-client-transport-${jetty.version}.jar
[lib]
lib/http2/jetty-http2-client-transport-${jetty.version}.jar

View File

@ -124,12 +124,6 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
connect(address, destination.getClientConnectionFactory(), listenerPromise, listenerPromise, context);
}
@Override
public void connect(InetSocketAddress address, Map<String, Object> context)
{
connect((SocketAddress)address, context);
}
protected void connect(SocketAddress address, ClientConnectionFactory factory, Session.Listener listener, Promise<Session> promise, Map<String, Object> context)
{
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);

View File

@ -44,6 +44,7 @@ import org.eclipse.jetty.http2.HTTP2Session;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.thread.Sweeper;
import org.slf4j.Logger;
@ -85,6 +86,12 @@ public class HttpConnectionOverHTTP2 extends HttpConnection implements Sweeper.S
return session.getRemoteSocketAddress();
}
@Override
public EndPoint.SslSessionData getSslSessionData()
{
return connection.getEndPoint().getSslSessionData();
}
public boolean isRecycleHttpChannels()
{
return recycleHttpChannels;

View File

@ -0,0 +1,18 @@
[description]
Adds the Jetty HTTP/2 client dependencies to the server classpath.
[tags]
client
http2
[files]
maven://org.eclipse.jetty/jetty-alpn-client/${jetty.version}/jar|lib/jetty-alpn-client-${jetty.version}.jar
maven://org.eclipse.jetty/jetty-alpn-java-client/${jetty.version}/jar|lib/jetty-alpn-java-client-${jetty.version}.jar
maven://org.eclipse.jetty.http2/jetty-http2-client/${jetty.version}/jar|lib/http2/jetty-http2-client-${jetty.version}.jar
[lib]
lib/jetty-alpn-client-${jetty.version}.jar
lib/jetty-alpn-java-client-${jetty.version}.jar
lib/http2/jetty-http2-client-${jetty.version}.jar
lib/http2/jetty-http2-common-${jetty.version}.jar
lib/http2/jetty-http2-hpack-${jetty.version}.jar

View File

@ -95,8 +95,7 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory
public void onOpen()
{
Map<Integer, Integer> settings = listener.onPreface(getSession());
if (settings == null)
settings = new HashMap<>();
settings = settings == null ? new HashMap<>() : new HashMap<>(settings);
// Below we want to populate any settings to send to the server
// that have a different default than what prescribed by the RFC.

View File

@ -1248,6 +1248,11 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements Session
this.stream = stream;
}
public Frame frame()
{
return frame;
}
public abstract int getFrameBytesGenerated();
public int getDataBytesRemaining()

View File

@ -14,7 +14,6 @@
package org.eclipse.jetty.http2;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
@ -54,30 +53,12 @@ public abstract class HTTP2StreamEndPoint implements EndPoint
this.stream = stream;
}
@Override
public InetSocketAddress getLocalAddress()
{
SocketAddress local = getLocalSocketAddress();
if (local instanceof InetSocketAddress)
return (InetSocketAddress)local;
return null;
}
@Override
public SocketAddress getLocalSocketAddress()
{
return stream.getSession().getLocalSocketAddress();
}
@Override
public InetSocketAddress getRemoteAddress()
{
SocketAddress remote = getRemoteSocketAddress();
if (remote instanceof InetSocketAddress)
return (InetSocketAddress)remote;
return null;
}
@Override
public SocketAddress getRemoteSocketAddress()
{

View File

@ -14,6 +14,7 @@
package org.eclipse.jetty.http2.internal;
import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
@ -24,10 +25,12 @@ import java.util.List;
import java.util.Queue;
import java.util.Set;
import org.eclipse.jetty.http2.ErrorCode;
import org.eclipse.jetty.http2.FlowControlStrategy;
import org.eclipse.jetty.http2.HTTP2Connection;
import org.eclipse.jetty.http2.HTTP2Session;
import org.eclipse.jetty.http2.HTTP2Stream;
import org.eclipse.jetty.http2.frames.FrameType;
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
import org.eclipse.jetty.http2.hpack.HpackException;
import org.eclipse.jetty.io.EndPoint;
@ -95,10 +98,9 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
entries.offerFirst(entry);
if (LOG.isDebugEnabled())
LOG.debug("Prepended {}, entries={}", entry, entries.size());
return true;
}
}
if (closed == null)
return true;
closed(entry, closed);
return false;
}
@ -109,15 +111,17 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
try (AutoLock ignored = lock.lock())
{
closed = terminated;
// If it was not possible to HPACK encode, then allow to send a GOAWAY.
if (closed instanceof HpackException.SessionException && entry.frame().getType() == FrameType.GO_AWAY)
closed = null;
if (closed == null)
{
entries.offer(entry);
if (LOG.isDebugEnabled())
LOG.debug("Appended {}, entries={}, {}", entry, entries.size(), this);
return true;
}
}
if (closed == null)
return true;
closed(entry, closed);
return false;
}
@ -133,10 +137,9 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
list.forEach(entries::offer);
if (LOG.isDebugEnabled())
LOG.debug("Appended {}, entries={} {}", list, entries.size(), this);
return true;
}
}
if (closed == null)
return true;
list.forEach(entry -> closed(entry, closed));
return false;
}
@ -166,7 +169,21 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
try (AutoLock ignored = lock.lock())
{
if (terminated != null)
throw terminated;
{
boolean rethrow = true;
if (terminated instanceof HpackException.SessionException)
{
HTTP2Session.Entry entry = entries.peek();
if (entry != null && entry.frame().getType() == FrameType.GO_AWAY)
{
// Allow a SessionException to be processed once to send a GOAWAY.
terminated = new ClosedChannelException().initCause(terminated);
rethrow = false;
}
}
if (rethrow)
throw terminated;
}
WindowEntry windowEntry;
while ((windowEntry = windows.poll()) != null)
@ -251,6 +268,15 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
entry.failed(failure);
pending.remove();
}
catch (HpackException.SessionException failure)
{
if (LOG.isDebugEnabled())
LOG.debug("Failure generating {}", entry, failure);
onSessionFailure(failure);
// The method above will try to send
// a GOAWAY, so we will iterate again.
return Action.IDLE;
}
catch (Throwable failure)
{
// Failure to generate the entry is catastrophic.
@ -343,11 +369,32 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
throw new IllegalStateException();
}
@Override
protected void onFailure(Throwable x)
{
Throwable closed = fail(x);
// If the failure came from within the
// flusher, we need to close the connection.
if (closed == null)
session.onWriteFailure(x);
}
@Override
protected void onCompleteFailure(Throwable x)
{
release();
}
private void onSessionFailure(Throwable x)
{
release();
Throwable closed = fail(x);
if (closed == null)
session.close(ErrorCode.COMPRESSION_ERROR.code, null, NOOP);
}
private Throwable fail(Throwable x)
{
Throwable closed;
Set<HTTP2Session.Entry> allEntries;
try (AutoLock ignored = lock.lock())
@ -369,11 +416,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
allEntries.addAll(pendingEntries);
pendingEntries.clear();
allEntries.forEach(entry -> entry.failed(x));
// If the failure came from within the
// flusher, we need to close the connection.
if (closed == null)
session.onWriteFailure(x);
return closed;
}
public void terminate(Throwable cause)

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_10_0.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure id="sslConnector" class="org.eclipse.jetty.server.ServerConnector">
<Call name="addConnectionFactory">

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_10_0.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure id="httpConnector" class="org.eclipse.jetty.server.ServerConnector">
<Call name="addConnectionFactory">

View File

@ -33,6 +33,7 @@ import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
import org.eclipse.jetty.io.AbstractEndPoint;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Request;
@ -45,8 +46,6 @@ import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import static java.util.concurrent.TimeUnit.SECONDS;
@ -55,7 +54,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Disabled // TODO fix this
public class BlockedWritesWithSmallThreadPoolTest
{
private Server server;
@ -102,7 +100,6 @@ public class BlockedWritesWithSmallThreadPoolTest
}
@Test
@Tag("flaky")
public void testServerThreadsBlockedInWrites() throws Exception
{
int contentLength = 16 * 1024 * 1024;
@ -110,11 +107,12 @@ public class BlockedWritesWithSmallThreadPoolTest
start(new Handler.Abstract()
{
@Override
public boolean handle(Request request, Response response, Callback callback)
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
serverEndPointRef.compareAndSet(null, (AbstractEndPoint)request.getConnectionMetaData().getConnection().getEndPoint());
// Write a large content to cause TCP congestion.
response.write(true, ByteBuffer.wrap(new byte[contentLength]), callback);
// Blocking write a large content to cause TCP congestion.
Content.Sink.write(response, true, ByteBuffer.wrap(new byte[contentLength]));
callback.succeeded();
return true;
}
});
@ -140,21 +138,20 @@ public class BlockedWritesWithSmallThreadPoolTest
@Override
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
try
{
// Block here to stop reading from the network
// to cause the server to TCP congest.
clientBlockLatch.await(5, SECONDS);
Stream.Data data = stream.readData();
data.release();
if (data.frame().isEndStream())
clientDataLatch.countDown();
else
stream.demand();
}
catch (InterruptedException x)
catch (InterruptedException ignored)
{
data.release();
}
}
});
@ -174,18 +171,139 @@ public class BlockedWritesWithSmallThreadPoolTest
await().atMost(5, SECONDS).until(() -> serverThreads.getAvailableReservedThreads() == 1);
}
// Use the reserved thread for a blocking operation, simulating another blocking write.
long delaySeconds = 10;
CountDownLatch serverBlockLatch = new CountDownLatch(1);
assertTrue(serverThreads.tryExecute(() -> await().atMost(20, SECONDS).until(() -> serverBlockLatch.await(15, SECONDS), b -> true)));
assertTrue(serverThreads.tryExecute(() ->
{
try
{
serverBlockLatch.await(2 * delaySeconds, SECONDS);
}
catch (InterruptedException ignored)
{
}
}));
// No more threads are available on the server.
assertEquals(0, serverThreads.getReadyThreads());
// Unblock the client to read from the network, which should unblock the server write().
clientBlockLatch.countDown();
assertTrue(clientDataLatch.await(10, SECONDS), server.dump());
assertTrue(clientDataLatch.await(delaySeconds, SECONDS), server.dump());
// Unblock blocked threads.
serverBlockLatch.countDown();
}
@Test
public void testServerThreadsInPendingWrites() throws Exception
{
int contentLength = 16 * 1024 * 1024;
AtomicReference<AbstractEndPoint> serverEndPointRef = new AtomicReference<>();
start(new Handler.Abstract()
{
@Override
public boolean handle(Request request, Response response, Callback callback)
{
serverEndPointRef.set((AbstractEndPoint)request.getConnectionMetaData().getConnection().getEndPoint());
// Large write that will TCP congest, but it is non-blocking.
response.write(true, ByteBuffer.allocate(contentLength), callback);
return true;
}
});
client = new HTTP2Client();
// Set large flow control windows so the server hits TCP congestion.
int window = 2 * contentLength;
client.setInitialSessionRecvWindow(window);
client.setInitialStreamRecvWindow(window);
client.start();
CountDownLatch clientBlockLatch = new CountDownLatch(1);
CountDownLatch clientDataLatch = new CountDownLatch(1);
Session session = client.connect(new InetSocketAddress("localhost", connector.getLocalPort()), new Session.Listener() {})
.get(5, SECONDS);
HttpURI uri = HttpURI.build("http://localhost:" + connector.getLocalPort() + "/congest");
MetaData.Request request = new MetaData.Request("GET", uri, HttpVersion.HTTP_2, HttpFields.EMPTY);
session.newStream(new HeadersFrame(request, null, true), new Stream.Listener()
{
@Override
public void onDataAvailable(Stream stream)
{
try
{
// Block here to stop reading from the network
// to cause the server to TCP congest.
clientBlockLatch.await(5, SECONDS);
Stream.Data data = stream.readData();
data.release();
if (data.frame().isEndStream())
clientDataLatch.countDown();
else
stream.demand();
}
catch (InterruptedException ignored)
{
}
}
});
await().atMost(5, SECONDS).until(() ->
{
AbstractEndPoint serverEndPoint = serverEndPointRef.get();
return serverEndPoint != null && serverEndPoint.getWriteFlusher().isPending();
});
// Wait for NIO on the server to be OP_WRITE interested.
Thread.sleep(1000);
// Handler.handle() should have returned, make sure we block that thread.
long delaySeconds = 10;
await().atMost(5, SECONDS).until(() -> serverThreads.getIdleThreads() == 1);
CountDownLatch serverBlockLatch = new CountDownLatch(1);
serverThreads.execute(() ->
{
try
{
serverBlockLatch.await(2 * delaySeconds, SECONDS);
}
catch (InterruptedException ignored)
{
}
});
// Make sure there is a reserved thread.
if (serverThreads.getAvailableReservedThreads() != 1)
{
assertFalse(serverThreads.tryExecute(() -> {}));
await().atMost(5, SECONDS).until(() -> serverThreads.getAvailableReservedThreads() == 1);
}
// Use the reserved thread for a blocking operation, simulating another blocking write.
CountDownLatch reservedBlockLatch = new CountDownLatch(1);
assertTrue(serverThreads.tryExecute(() ->
{
try
{
reservedBlockLatch.await(2 * delaySeconds, SECONDS);
}
catch (InterruptedException ignored)
{
}
}));
// No more threads are available on the server.
assertEquals(0, serverThreads.getReadyThreads());
// Unblock the client to read from the network, which must unblock the server write() and send a response.
clientBlockLatch.countDown();
assertTrue(clientDataLatch.await(delaySeconds, SECONDS), server.dump());
// Unblock blocked threads.
serverBlockLatch.countDown();
reservedBlockLatch.countDown();
}
@Test
public void testClientThreadsBlockedInWrite() throws Exception
{
@ -202,12 +320,12 @@ public class BlockedWritesWithSmallThreadPoolTest
@Override
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
try
{
// Block here to stop reading from the network
// to cause the client to TCP congest.
serverBlockLatch.await(5, SECONDS);
Stream.Data data = stream.readData();
data.release();
if (data.frame().isEndStream())
{
@ -219,9 +337,8 @@ public class BlockedWritesWithSmallThreadPoolTest
stream.demand();
}
}
catch (InterruptedException x)
catch (InterruptedException ignored)
{
data.release();
}
}
};
@ -279,14 +396,27 @@ public class BlockedWritesWithSmallThreadPoolTest
await().atMost(5, SECONDS).until(() -> clientThreads.getAvailableReservedThreads() == 1);
}
// Use the reserved thread for a blocking operation, simulating another blocking write.
assertTrue(clientThreads.tryExecute(() -> await().until(() -> clientBlockLatch.await(15, SECONDS), b -> true)));
long delaySeconds = 10;
assertTrue(clientThreads.tryExecute(() ->
{
try
{
clientBlockLatch.await(2 * delaySeconds, SECONDS);
}
catch (InterruptedException ignored)
{
}
}));
// No more threads are available on the client.
await().atMost(5, SECONDS).until(() -> clientThreads.getReadyThreads() == 0);
// Unblock the server to read from the network, which should unblock the client.
serverBlockLatch.countDown();
assertTrue(latch.await(10, SECONDS), client.dump());
assertTrue(latch.await(delaySeconds, SECONDS), client.dump());
// Unblock blocked threads.
clientBlockLatch.countDown();
}
}

View File

@ -403,11 +403,20 @@ public class HTTP2ServerTest extends AbstractServerTest
accumulator.writeTo(Content.Sink.from(output), false);
output.flush();
AtomicBoolean goAway = new AtomicBoolean();
Parser parser = new Parser(bufferPool, 8192);
parser.init(new Parser.Listener() {});
parser.init(new Parser.Listener()
{
@Override
public void onGoAway(GoAwayFrame frame)
{
goAway.set(true);
}
});
boolean closed = parseResponse(client, parser);
assertTrue(closed);
assertFalse(closed);
assertTrue(goAway.get());
}
}
}

View File

@ -519,7 +519,7 @@ public class RawHTTP2ProxyTest
}
@Override
protected void onCompleteFailure(Throwable cause)
protected void onFailure(Throwable cause)
{
frameInfo.callback.failed(cause);
}
@ -673,7 +673,7 @@ public class RawHTTP2ProxyTest
}
@Override
protected void onCompleteFailure(Throwable cause)
protected void onFailure(Throwable cause)
{
frameInfo.callback.failed(cause);
}

View File

@ -16,7 +16,9 @@ package org.eclipse.jetty.http2.tests;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@ -37,9 +39,17 @@ import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.hpack.HpackException;
import org.eclipse.jetty.io.RetainableByteBuffer;
import org.eclipse.jetty.util.Callback;
import org.junit.jupiter.api.Assertions;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class SettingsTest extends AbstractTest
{
@Test
@ -107,11 +117,11 @@ public class SettingsTest extends AbstractTest
.flip();
((HTTP2Session)clientSession).getEndPoint().write(Callback.NOOP, byteBuffer);
Assertions.assertFalse(serverSettingsLatch.get().await(1, TimeUnit.SECONDS));
Assertions.assertTrue(serverFailureLatch.await(5, TimeUnit.SECONDS));
Assertions.assertTrue(serverCloseLatch.await(5, TimeUnit.SECONDS));
Assertions.assertTrue(clientGoAwayLatch.await(5, TimeUnit.SECONDS));
Assertions.assertTrue(clientCloseLatch.await(5, TimeUnit.SECONDS));
assertFalse(serverSettingsLatch.get().await(1, TimeUnit.SECONDS));
assertTrue(serverFailureLatch.await(5, TimeUnit.SECONDS));
assertTrue(serverCloseLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientGoAwayLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientCloseLatch.await(5, TimeUnit.SECONDS));
}
@Test
@ -181,11 +191,11 @@ public class SettingsTest extends AbstractTest
.flip();
((HTTP2Session)clientSession).getEndPoint().write(Callback.NOOP, byteBuffer);
Assertions.assertFalse(serverSettingsLatch.get().await(1, TimeUnit.SECONDS));
Assertions.assertTrue(serverFailureLatch.await(5, TimeUnit.SECONDS));
Assertions.assertTrue(serverCloseLatch.await(5, TimeUnit.SECONDS));
Assertions.assertTrue(clientGoAwayLatch.await(5, TimeUnit.SECONDS));
Assertions.assertTrue(clientCloseLatch.await(5, TimeUnit.SECONDS));
assertFalse(serverSettingsLatch.get().await(1, TimeUnit.SECONDS));
assertTrue(serverFailureLatch.await(5, TimeUnit.SECONDS));
assertTrue(serverCloseLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientGoAwayLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientCloseLatch.await(5, TimeUnit.SECONDS));
}
@Test
@ -213,7 +223,7 @@ public class SettingsTest extends AbstractTest
}
});
Assertions.assertTrue(serverFailureLatch.await(5, TimeUnit.SECONDS));
assertTrue(serverFailureLatch.await(5, TimeUnit.SECONDS));
}
@Test
@ -242,7 +252,7 @@ public class SettingsTest extends AbstractTest
}
});
Assertions.assertTrue(clientFailureLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientFailureLatch.await(5, TimeUnit.SECONDS));
}
@Test
@ -304,9 +314,9 @@ public class SettingsTest extends AbstractTest
}
});
Assertions.assertTrue(serverPushFailureLatch.await(5, TimeUnit.SECONDS));
Assertions.assertTrue(clientResponseLatch.await(5, TimeUnit.SECONDS));
Assertions.assertFalse(clientPushLatch.await(1, TimeUnit.SECONDS));
assertTrue(serverPushFailureLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientResponseLatch.await(5, TimeUnit.SECONDS));
assertFalse(clientPushLatch.await(1, TimeUnit.SECONDS));
}
@Test
@ -362,6 +372,122 @@ public class SettingsTest extends AbstractTest
HeadersFrame frame = new HeadersFrame(request, null, true);
clientSession.newStream(frame, Stream.Listener.AUTO_DISCARD);
Assertions.assertTrue(clientFailureLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientFailureLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testMaxHeaderListSizeExceededServerSendsGoAway() throws Exception
{
int maxHeadersSize = 512;
start(new ServerSessionListener()
{
@Override
public void onSettings(Session session, SettingsFrame frame)
{
((HTTP2Session)session).getParser().getHpackDecoder().setMaxHeaderListSize(maxHeadersSize);
}
});
CountDownLatch goAwayLatch = new CountDownLatch(1);
Session clientSession = newClientSession(new Session.Listener()
{
@Override
public void onGoAway(Session session, GoAwayFrame frame)
{
goAwayLatch.countDown();
}
});
HttpFields requestHeaders = HttpFields.build()
.put("X-Large", "x".repeat(maxHeadersSize * 2));
MetaData.Request request = newRequest("GET", requestHeaders);
HeadersFrame frame = new HeadersFrame(request, null, true);
Stream stream = clientSession.newStream(frame, new Stream.Listener() {}).get(5, TimeUnit.SECONDS);
// The request can be sent by the client, the server will reject it.
// The spec suggests to send 431, but we do not want to "taint" the
// HPACK context with large headers.
assertNotNull(stream);
// The server should send a GOAWAY.
assertTrue(goAwayLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testMaxHeaderListSizeExceededByClient() throws Exception
{
int maxHeadersSize = 512;
CountDownLatch goAwayLatch = new CountDownLatch(1);
start(new ServerSessionListener()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
{
return Map.of(SettingsFrame.MAX_HEADER_LIST_SIZE, maxHeadersSize);
}
@Override
public void onGoAway(Session session, GoAwayFrame frame)
{
goAwayLatch.countDown();
}
});
Session clientSession = newClientSession(new Session.Listener() {});
HttpFields requestHeaders = HttpFields.build()
.put("X-Large", "x".repeat(maxHeadersSize * 2));
MetaData.Request request = newRequest("GET", requestHeaders);
HeadersFrame frame = new HeadersFrame(request, null, true);
Throwable failure = assertThrows(ExecutionException.class,
() -> clientSession.newStream(frame, new Stream.Listener() {}).get(5, TimeUnit.SECONDS))
.getCause();
// The HPACK context is compromised trying to encode the large header.
assertThat(failure, Matchers.instanceOf(HpackException.SessionException.class));
assertTrue(goAwayLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testMaxHeaderListSizeExceededByServer() throws Exception
{
int maxHeadersSize = 512;
AtomicReference<CompletableFuture<Stream>> responseRef = new AtomicReference<>();
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
HttpFields responseHeaders = HttpFields.build()
.put("X-Large", "x".repeat(maxHeadersSize * 2));
MetaData.Response response = new MetaData.Response(HttpStatus.OK_200, null, HttpVersion.HTTP_2, responseHeaders);
responseRef.set(stream.headers(new HeadersFrame(stream.getId(), response, null, true)));
return null;
}
});
CountDownLatch goAwayLatch = new CountDownLatch(1);
Session clientSession = newClientSession(new Session.Listener()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
{
return Map.of(SettingsFrame.MAX_HEADER_LIST_SIZE, maxHeadersSize);
}
@Override
public void onGoAway(Session session, GoAwayFrame frame)
{
goAwayLatch.countDown();
}
});
MetaData.Request request = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(request, null, true);
clientSession.newStream(frame, new Stream.Listener() {});
CompletableFuture<Stream> completable = await().atMost(5, TimeUnit.SECONDS).until(responseRef::get, notNullValue());
Throwable failure = assertThrows(ExecutionException.class, () -> completable.get(5, TimeUnit.SECONDS)).getCause();
assertThat(failure, Matchers.instanceOf(HpackException.SessionException.class));
assertTrue(goAwayLatch.await(5, TimeUnit.SECONDS));
}
}

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.http3.client.transport;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.time.Duration;
import java.util.List;
@ -99,12 +98,6 @@ public class HttpClientTransportOverHTTP3 extends AbstractHttpClientTransport im
return new HttpDestination(getHttpClient(), origin);
}
@Override
public void connect(InetSocketAddress address, Map<String, Object> context)
{
connect((SocketAddress)address, context);
}
@Override
public void connect(SocketAddress address, Map<String, Object> context)
{

View File

@ -30,6 +30,8 @@ import org.eclipse.jetty.client.transport.HttpRequest;
import org.eclipse.jetty.client.transport.SendFailure;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http3.client.HTTP3SessionClient;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.quic.common.QuicSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -64,6 +66,13 @@ public class HttpConnectionOverHTTP3 extends HttpConnection implements Connectio
return session.getRemoteSocketAddress();
}
@Override
public EndPoint.SslSessionData getSslSessionData()
{
QuicSession quicSession = getSession().getProtocolSession().getQuicSession();
return EndPoint.SslSessionData.from(null, null, null, quicSession.getPeerCertificates());
}
@Override
public int getMaxMultiplex()
{

View File

@ -122,13 +122,11 @@ public class ControlFlusher extends IteratingCallback
}
@Override
protected void onCompleteFailure(Throwable failure)
protected void onFailure(Throwable failure)
{
if (LOG.isDebugEnabled())
LOG.debug("failed to write {} on {}", entries, this, failure);
accumulator.release();
List<Entry> allEntries = new ArrayList<>(entries);
entries.clear();
try (AutoLock ignored = lock.lock())
@ -147,6 +145,12 @@ public class ControlFlusher extends IteratingCallback
endPoint.getQuicSession().getProtocolSession().outwardClose(error, "control_stream_failure");
}
@Override
protected void onCompleteFailure(Throwable cause)
{
accumulator.release();
}
@Override
public InvocationType getInvocationType()
{

View File

@ -118,13 +118,11 @@ public class InstructionFlusher extends IteratingCallback
}
@Override
protected void onCompleteFailure(Throwable failure)
protected void onFailure(Throwable failure)
{
if (LOG.isDebugEnabled())
LOG.debug("failed to write buffers on {}", this, failure);
accumulator.release();
try (AutoLock ignored = lock.lock())
{
terminated = failure;
@ -138,6 +136,12 @@ public class InstructionFlusher extends IteratingCallback
endPoint.getQuicSession().getProtocolSession().outwardClose(error, "instruction_stream_failure");
}
@Override
protected void onCompleteFailure(Throwable cause)
{
accumulator.release();
}
@Override
public InvocationType getInvocationType()
{

View File

@ -118,13 +118,11 @@ public class MessageFlusher extends IteratingCallback
}
@Override
protected void onCompleteFailure(Throwable cause)
protected void onFailure(Throwable cause)
{
if (LOG.isDebugEnabled())
LOG.debug("failed to write {} on {}", entry, this, cause);
accumulator.release();
if (entry != null)
{
entry.callback.failed(cause);
@ -132,6 +130,12 @@ public class MessageFlusher extends IteratingCallback
}
}
@Override
protected void onCompleteFailure(Throwable cause)
{
accumulator.release();
}
@Override
public InvocationType getInvocationType()
{

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_10_0.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call name="addConnector">

View File

@ -145,6 +145,20 @@ public abstract class AbstractConnection implements Connection, Invocable
getEndPoint().fillInterested(_readCallback);
}
/**
* <p>Utility method to be called to register read interest.</p>
* <p>After a call to this method, {@link #onFillable()} or {@link #onFillInterestedFailed(Throwable)}
* will be called back as appropriate.</p>
*
* @see #onFillable()
*/
public void fillInterested(Callback callback)
{
if (LOG.isDebugEnabled())
LOG.debug("fillInterested {} {}", callback, this);
getEndPoint().fillInterested(callback);
}
public void tryFillInterested(Callback callback)
{
getEndPoint().tryFillInterested(callback);

View File

@ -14,7 +14,6 @@
package org.eclipse.jetty.io;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.WritePendingException;
@ -59,27 +58,9 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint
super(scheduler);
}
@Override
public InetSocketAddress getLocalAddress()
{
SocketAddress local = getLocalSocketAddress();
if (local instanceof InetSocketAddress)
return (InetSocketAddress)local;
return null;
}
@Override
public abstract SocketAddress getLocalSocketAddress();
@Override
public InetSocketAddress getRemoteAddress()
{
SocketAddress remote = getRemoteSocketAddress();
if (remote instanceof InetSocketAddress)
return (InetSocketAddress)remote;
return null;
}
@Override
public abstract SocketAddress getRemoteSocketAddress();

View File

@ -224,7 +224,7 @@ public class ArrayByteBufferPool implements ByteBufferPool, Dumpable
}
@Override
public boolean removeAndRelease(RetainableByteBuffer buffer)
public boolean releaseAndRemove(RetainableByteBuffer buffer)
{
RetainableByteBuffer actual = buffer;
while (actual instanceof RetainableByteBuffer.Wrapper wrapper)
@ -244,7 +244,7 @@ public class ArrayByteBufferPool implements ByteBufferPool, Dumpable
return buffer.release();
}
return ByteBufferPool.super.removeAndRelease(buffer);
return ByteBufferPool.super.releaseAndRemove(buffer);
}
private void reserve(RetainedBucket bucket, ByteBuffer byteBuffer)

View File

@ -64,11 +64,9 @@ public interface ByteBufferPool
* If the buffer is not in a pool, calling this method is equivalent to calling {@link RetainableByteBuffer#release()}.
* Calling this method satisfies any contract that requires a call to {@link RetainableByteBuffer#release()}.
* @return {@code true} if a call to {@link RetainableByteBuffer#release()} would have returned {@code true}.
* @see RetainableByteBuffer#release()
* @deprecated This API is experimental and may be removed in future releases
* @see RetainableByteBuffer#releaseAndRemove()
*/
@Deprecated
default boolean removeAndRelease(RetainableByteBuffer buffer)
default boolean releaseAndRemove(RetainableByteBuffer buffer)
{
return buffer != null && buffer.release();
}
@ -96,6 +94,12 @@ public interface ByteBufferPool
return wrapped;
}
@Override
public boolean releaseAndRemove(RetainableByteBuffer buffer)
{
return getWrapped().releaseAndRemove(buffer);
}
@Override
public RetainableByteBuffer.Mutable acquire(int size, boolean direct)
{

View File

@ -18,18 +18,14 @@ import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketOption;
import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions;
import java.net.UnixDomainSocketAddress;
import java.nio.channels.DatagramChannel;
import java.nio.channels.NetworkChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
import org.eclipse.jetty.util.IO;
@ -79,20 +75,6 @@ public class ClientConnector extends ContainerLifeCycle
public static final String APPLICATION_PROTOCOLS_CONTEXT_KEY = CLIENT_CONNECTOR_CONTEXT_KEY + ".applicationProtocols";
private static final Logger LOG = LoggerFactory.getLogger(ClientConnector.class);
/**
* <p>Creates a ClientConnector configured to connect via Unix-Domain sockets to the given Unix-Domain path</p>
*
* @param path the Unix-Domain path to connect to
* @return a ClientConnector that connects to the given Unix-Domain path
* @deprecated replaced by {@link Transport.TCPUnix}
*/
@Deprecated(since = "12.0.7", forRemoval = true)
public static ClientConnector forUnixDomain(Path path)
{
return new ClientConnector(Configurator.forUnixDomain(path));
}
private final Configurator configurator;
private Executor executor;
private Scheduler scheduler;
private ByteBufferPool byteBufferPool;
@ -109,36 +91,6 @@ public class ClientConnector extends ContainerLifeCycle
private int receiveBufferSize = -1;
private int sendBufferSize = -1;
public ClientConnector()
{
this(new Configurator());
}
/**
* @param configurator the {@link Configurator}
* @deprecated replaced by {@link Transport}
*/
@Deprecated(since = "12.0.7", forRemoval = true)
public ClientConnector(Configurator configurator)
{
this.configurator = Objects.requireNonNull(configurator);
installBean(configurator);
configurator.addBean(this, false);
}
/**
* @param address the SocketAddress to connect to
* @return whether the connection to the given SocketAddress is intrinsically secure
* @see Configurator#isIntrinsicallySecure(ClientConnector, SocketAddress)
*
* @deprecated replaced by {@link Transport#isIntrinsicallySecure()}
*/
@Deprecated(since = "12.0.7", forRemoval = true)
public boolean isIntrinsicallySecure(SocketAddress address)
{
return configurator.isIntrinsicallySecure(this, address);
}
public SelectorManager getSelectorManager()
{
return selectorManager;
@ -149,21 +101,6 @@ public class ClientConnector extends ContainerLifeCycle
return executor;
}
/**
* <p>Returns the default {@link Transport} for this connector.</p>
* <p>This method only exists for backwards compatibility, when
* {@link Configurator} was used, and should be removed when
* {@link Configurator} is removed.</p>
*
* @return the default {@link Transport} for this connector
* @deprecated use {@link Transport} instead
*/
@Deprecated(since = "12.0.7", forRemoval = true)
public Transport newTransport()
{
return configurator.newTransport();
}
public void setExecutor(Executor executor)
{
if (isStarted())
@ -632,125 +569,4 @@ public class ClientConnector extends ContainerLifeCycle
connectFailed(failure, context);
}
}
/**
* <p>Configures a {@link ClientConnector}.</p>
*
* @deprecated replaced by {@link Transport}
*/
@Deprecated(since = "12.0.7", forRemoval = true)
public static class Configurator extends ContainerLifeCycle
{
/**
* @return the default {@link Transport} for this configurator
*/
public Transport newTransport()
{
return null;
}
/**
* <p>Returns whether the connection to a given {@link SocketAddress} is intrinsically secure.</p>
* <p>A protocol such as HTTP/1.1 can be transported by TCP; however, TCP is not secure because
* it does not offer any encryption.</p>
* <p>Encryption is provided by using TLS to wrap the HTTP/1.1 bytes, and then transporting the
* TLS bytes over TCP.</p>
* <p>On the other hand, protocols such as QUIC are intrinsically secure, and therefore it is
* not necessary to wrap the HTTP/1.1 bytes with TLS: the HTTP/1.1 bytes are transported over
* QUIC in an intrinsically secure way.</p>
*
* @param clientConnector the ClientConnector
* @param address the SocketAddress to connect to
* @return whether the connection to the given SocketAddress is intrinsically secure
*/
public boolean isIntrinsicallySecure(ClientConnector clientConnector, SocketAddress address)
{
return false;
}
/**
* <p>Creates a new {@link SocketChannel} to connect to a {@link SocketAddress}
* derived from the input socket address.</p>
* <p>The input socket address represents the destination socket address to
* connect to, as it is typically specified by a URI authority, for example
* {@code localhost:8080} if the URI is {@code http://localhost:8080/path}.</p>
* <p>However, the returned socket address may be different as the implementation
* may use a Unix-Domain socket address to physically connect to the virtual
* destination socket address given as input.</p>
* <p>The return type is a pair/record holding the socket channel and the
* socket address, with the socket channel not yet connected.
* The implementation of this methods must not call
* {@link SocketChannel#connect(SocketAddress)}, as this is done later,
* after configuring the socket, by the {@link ClientConnector} implementation.</p>
*
* @param clientConnector the client connector requesting channel with associated address
* @param address the destination socket address, typically specified in a URI
* @param context the context to create the new socket channel
* @return a new {@link SocketChannel} with an associated {@link SocketAddress} to connect to
* @throws IOException if the socket channel or the socket address cannot be created
*/
public ChannelWithAddress newChannelWithAddress(ClientConnector clientConnector, SocketAddress address, Map<String, Object> context) throws IOException
{
return new ChannelWithAddress(SocketChannel.open(), address);
}
public EndPoint newEndPoint(ClientConnector clientConnector, SocketAddress address, SelectableChannel selectable, ManagedSelector selector, SelectionKey selectionKey)
{
return new SocketChannelEndPoint((SocketChannel)selectable, selector, selectionKey, clientConnector.getScheduler());
}
public Connection newConnection(ClientConnector clientConnector, SocketAddress address, EndPoint endPoint, Map<String, Object> context) throws IOException
{
ClientConnectionFactory factory = (ClientConnectionFactory)context.get(CLIENT_CONNECTION_FACTORY_CONTEXT_KEY);
return factory.newConnection(endPoint, context);
}
/**
* <p>A pair/record holding a {@link SelectableChannel} and a {@link SocketAddress} to connect to.</p>
*
* @deprecated replaced by {@link Transport}
*/
@Deprecated(since = "12.0.7", forRemoval = true)
public static class ChannelWithAddress
{
private final SelectableChannel channel;
private final SocketAddress address;
public ChannelWithAddress(SelectableChannel channel, SocketAddress address)
{
this.channel = channel;
this.address = address;
}
public SelectableChannel getSelectableChannel()
{
return channel;
}
public SocketAddress getSocketAddress()
{
return address;
}
}
private static Configurator forUnixDomain(Path path)
{
return new Configurator()
{
@Override
public Transport newTransport()
{
return new Transport.TCPUnix(path);
}
@Override
public ChannelWithAddress newChannelWithAddress(ClientConnector clientConnector, SocketAddress address, Map<String, Object> context) throws IOException
{
SocketChannel socketChannel = SocketChannel.open(StandardProtocolFamily.UNIX);
UnixDomainSocketAddress socketAddress = UnixDomainSocketAddress.of(path);
return new ChannelWithAddress(socketChannel, socketAddress);
}
};
}
}
}

View File

@ -1080,6 +1080,27 @@ public class Content
return null;
}
/**
* Convenience method to release a chunk and return {@link #next(Chunk)}.
* Equivalent to:
* <pre>{@code
* if (chunk != null)
* {
* chunk.release();
* chunk = Chunk.next(chunk);
* }
* }</pre>
* @param chunk The chunk to release or {@code null}
* @return The {@link #next(Chunk)} chunk;
*/
static Chunk releaseAndNext(Chunk chunk)
{
if (chunk == null)
return null;
chunk.release();
return next(chunk);
}
/**
* @param chunk The chunk to test for an {@link Chunk#getFailure() failure}.
* @return True if the chunk is non-null and {@link Chunk#getFailure() chunk.getError()} returns non-null.

View File

@ -84,38 +84,22 @@ public interface EndPoint extends Closeable, Content.Sink
EndPoint unwrap();
}
/**
* @return The local InetSocketAddress to which this {@code EndPoint} is bound, or {@code null}
* if this {@code EndPoint} is not bound to a Socket address.
* @deprecated use {@link #getLocalSocketAddress()} instead
*/
@Deprecated
InetSocketAddress getLocalAddress();
/**
* @return the local SocketAddress to which this {@code EndPoint} is bound or {@code null}
* if this {@code EndPoint} is not bound to a Socket address.
*/
default SocketAddress getLocalSocketAddress()
{
return getLocalAddress();
return null;
}
/**
* @return The remote InetSocketAddress to which this {@code EndPoint} is connected, or {@code null}
* if this {@code EndPoint} is not connected to a Socket address.
* @deprecated use {@link #getRemoteSocketAddress()} instead.
*/
@Deprecated
InetSocketAddress getRemoteAddress();
/**
* @return The remote SocketAddress to which this {@code EndPoint} is connected, or {@code null}
* if this {@code EndPoint} is not connected to a Socket address.
*/
default SocketAddress getRemoteSocketAddress()
{
return getRemoteAddress();
return null;
}
/**

View File

@ -321,12 +321,18 @@ public class IOResources
}
@Override
protected void onCompleteFailure(Throwable x)
protected void onFailure(Throwable x)
{
IO.close(channel);
super.onFailure(x);
}
@Override
protected void onCompleteFailure(Throwable cause)
{
if (retainableByteBuffer != null)
retainableByteBuffer.release();
IO.close(channel);
super.onCompleteFailure(x);
super.onCompleteFailure(cause);
}
}
}

View File

@ -247,4 +247,30 @@ public interface Retainable
return String.format("%s@%x[r=%d]", getClass().getSimpleName(), hashCode(), get());
}
}
/**
* Convenience method that replaces code like:
* <pre>{@code
* if (buffer != null)
* {
* buffer.release();
* buffer = null;
* }
* }
* </pre>
* with:
* <pre>{@code
* buffer = Retainable.release(buffer);
* }
* </pre>
* @param retainable The retainable to release, if not {@code null}.
* @param <R> The type of the retainable
* @return always returns {@code null}
*/
static <R extends Retainable> R release(R retainable)
{
if (retainable != null)
retainable.release();
return null;
}
}

View File

@ -164,6 +164,19 @@ public interface RetainableByteBuffer extends Retainable
throw new ReadOnlyBufferException();
}
/**
* {@link #release() Releases} the buffer in a way that ensures it will not be recycled in a buffer pool.
* This method should be used in cases where it is unclear if operations on the buffer have completed
* (for example, when a write operation has been aborted asynchronously or timed out, but the write
* operation may still be pending).
* @return whether if the buffer was released.
* @see ByteBufferPool#releaseAndRemove(RetainableByteBuffer)
*/
default boolean releaseAndRemove()
{
return release();
}
/**
* Appends and consumes the contents of this buffer to the passed buffer, limited by the capacity of the target buffer.
* @param buffer The buffer to append bytes to, whose limit will be updated.
@ -657,6 +670,12 @@ public interface RetainableByteBuffer extends Retainable
return (RetainableByteBuffer)super.getWrapped();
}
@Override
public boolean releaseAndRemove()
{
return getWrapped().releaseAndRemove();
}
@Override
public boolean isRetained()
{
@ -1301,6 +1320,12 @@ public interface RetainableByteBuffer extends Retainable
_pool = pool;
}
@Override
public boolean releaseAndRemove()
{
return _pool.releaseAndRemove(this);
}
@Override
public RetainableByteBuffer slice(long length)
{
@ -1939,6 +1964,22 @@ public interface RetainableByteBuffer extends Retainable
return false;
}
@Override
public boolean releaseAndRemove()
{
if (LOG.isDebugEnabled())
LOG.debug("release {}", this);
if (super.release())
{
for (RetainableByteBuffer buffer : _buffers)
buffer.releaseAndRemove();
_buffers.clear();
_aggregate = null;
return true;
}
return false;
}
@Override
public void clear()
{
@ -2333,10 +2374,6 @@ public interface RetainableByteBuffer extends Retainable
@Override
protected Action process()
{
// release the last buffer written
if (_buffer != null)
_buffer.release();
// write next buffer
if (_index < _buffers.size())
{
@ -2357,6 +2394,20 @@ public interface RetainableByteBuffer extends Retainable
_buffers.clear();
return Action.SUCCEEDED;
}
@Override
protected void onSuccess()
{
// release the last buffer written
_buffer = Retainable.release(_buffer);
}
@Override
protected void onCompleteFailure(Throwable x)
{
// release the last buffer written
_buffer = Retainable.release(_buffer);
}
}.iterate();
}
}

View File

@ -145,7 +145,7 @@ public abstract class ByteBufferChunk extends RetainableByteBuffer.FixedCapacity
public WithRetainable(ByteBuffer byteBuffer, boolean last, Retainable retainable)
{
super(byteBuffer, last);
this.retainable = retainable;
this.retainable = Objects.requireNonNull(retainable);
}
@Override

View File

@ -27,7 +27,7 @@ public class ContentCopier extends IteratingNestedCallback
private final Content.Source source;
private final Content.Sink sink;
private final Content.Chunk.Processor chunkProcessor;
private Content.Chunk current;
private Content.Chunk chunk;
private boolean terminated;
public ContentCopier(Content.Source source, Content.Sink sink, Content.Chunk.Processor chunkProcessor, Callback callback)
@ -47,43 +47,47 @@ public class ContentCopier extends IteratingNestedCallback
@Override
protected Action process() throws Throwable
{
if (current != null)
current.release();
if (terminated)
return Action.SUCCEEDED;
current = source.read();
chunk = source.read();
if (current == null)
if (chunk == null)
{
source.demand(this::succeeded);
return Action.SCHEDULED;
}
if (chunkProcessor != null && chunkProcessor.process(current, this))
if (chunkProcessor != null && chunkProcessor.process(chunk, this))
return Action.SCHEDULED;
terminated = current.isLast();
terminated = chunk.isLast();
if (Content.Chunk.isFailure(current))
if (Content.Chunk.isFailure(chunk))
{
failed(current.getFailure());
failed(chunk.getFailure());
return Action.SCHEDULED;
}
sink.write(current.isLast(), current.getByteBuffer(), this);
sink.write(chunk.isLast(), chunk.getByteBuffer(), this);
return Action.SCHEDULED;
}
@Override
protected void onSuccess()
{
chunk = Content.Chunk.releaseAndNext(chunk);
}
@Override
protected void onFailure(Throwable cause)
{
ExceptionUtil.callAndThen(cause, source::fail, super::onFailure);
}
@Override
protected void onCompleteFailure(Throwable x)
{
if (current != null)
{
current.release();
current = Content.Chunk.next(current);
}
ExceptionUtil.callAndThen(x, source::fail, super::onCompleteFailure);
chunk = Content.Chunk.releaseAndNext(chunk);
}
}

View File

@ -448,7 +448,7 @@ public class ArrayByteBufferPoolTest
}
@Test
public void testRemoveAndRelease()
public void testReleaseAndRemove()
{
ArrayByteBufferPool pool = new ArrayByteBufferPool();
@ -471,9 +471,9 @@ public class ArrayByteBufferPoolTest
retained1 = pool.acquire(1024, false);
retained1.retain();
assertTrue(pool.removeAndRelease(reserved1));
assertTrue(pool.removeAndRelease(acquired1));
assertFalse(pool.removeAndRelease(retained1));
assertTrue(reserved1.releaseAndRemove());
assertTrue(acquired1.releaseAndRemove());
assertFalse(retained1.releaseAndRemove());
assertTrue(retained1.release());
assertThat(pool.getHeapByteBufferCount(), is(2L));

View File

@ -1,5 +1,5 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_10_0.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call name="addBean">

View File

@ -1,5 +1,5 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_10_0.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">

View File

@ -19,7 +19,7 @@
~
-->
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_10_0.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure>
<Ref refid="JMXConnectorServer">

View File

@ -19,7 +19,7 @@
~
-->
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_10_0.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure>
<Ref refid="JMXConnectorServer">

View File

@ -1,4 +1,4 @@
# DO NOT EDIT THIS FILE - See: https://eclipse.dev/jetty/documentation/
# DO NOT EDIT THIS FILE - See: https://jetty.org/docs/
[description]
Adds the Jetty JNDI implementation to the classpath.

View File

@ -12,7 +12,6 @@
<description>Test keystore with self-signed SSL Certificate.</description>
<properties>
<bouncycastle.version>1.77</bouncycastle.version>
<bundle-symbolic-name>${project.groupId}.keystore</bundle-symbolic-name>
</properties>
@ -20,17 +19,14 @@
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15to18</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcutil-jdk15to18</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>

View File

@ -198,6 +198,15 @@ public abstract class AbstractHomeForker extends AbstractForker
if (stopKey != null)
cmd.add("-DSTOP.KEY=" + stopKey);
//put any jetty properties onto the command line
if (jettyProperties != null)
{
for (Map.Entry<String, String> e : jettyProperties.entrySet())
{
cmd.add(e.getKey() + "=" + e.getValue());
}
}
//set up enabled jetty modules
StringBuilder tmp = new StringBuilder();
tmp.append("--module=");
@ -214,6 +223,7 @@ public abstract class AbstractHomeForker extends AbstractForker
if (libExtJarFiles != null && !libExtJarFiles.isEmpty() && tmp.indexOf("ext") < 0)
tmp.append(",ext");
tmp.append("," + environment + "-maven");
cmd.add(tmp.toString());
//put any other jetty options onto the command line
@ -222,15 +232,6 @@ public abstract class AbstractHomeForker extends AbstractForker
Arrays.stream(jettyOptions.split(" ")).filter(a -> StringUtil.isNotBlank(a)).forEach((a) -> cmd.add(a.trim()));
}
//put any jetty properties onto the command line
if (jettyProperties != null)
{
for (Map.Entry<String, String> e : jettyProperties.entrySet())
{
cmd.add(e.getKey() + "=" + e.getValue());
}
}
//existence of this file signals process started
cmd.add("jetty.token.file=" + tokenFile.getAbsolutePath().toString());

View File

@ -1,4 +1,4 @@
# DO NOT EDIT THIS FILE - See: https://eclipse.dev/jetty/documentation/
# DO NOT EDIT THIS FILE - See: https://jetty.org/docs/
[description]
Adds OpenId Connect authentication to the server.

View File

@ -1,4 +1,4 @@
# DO NOT EDIT THIS FILE - See: https://eclipse.dev/jetty/documentation/
# DO NOT EDIT THIS FILE - See: https://jetty.org/docs/
[description]
Adds the Jetty Plus JNDI support to the classpath.

View File

@ -1,136 +0,0 @@
//
// ========================================================================
// 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.quic.client;
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Map;
import java.util.Objects;
import java.util.function.UnaryOperator;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.DatagramChannelEndPoint;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.SocketChannelEndPoint;
import org.eclipse.jetty.io.Transport;
import org.eclipse.jetty.quic.common.QuicConfiguration;
import org.eclipse.jetty.util.ssl.SslContextFactory;
/**
* <p>A QUIC specific {@link ClientConnector.Configurator}.</p>
* <p>Since QUIC is based on UDP, this class creates {@link DatagramChannel}s instead of
* {@link SocketChannel}s, and {@link DatagramChannelEndPoint}s instead of
* {@link SocketChannelEndPoint}s.</p>
*
* @see QuicConfiguration
* @deprecated replaced by {@link Transport}
*/
@Deprecated(since = "12.0.7", forRemoval = true)
public class QuicClientConnectorConfigurator extends ClientConnector.Configurator
{
private final QuicConfiguration initQuicConfig = new QuicConfiguration();
private final UnaryOperator<Connection> configurator;
private ClientQuicConfiguration quicConfig;
public QuicClientConnectorConfigurator()
{
this(UnaryOperator.identity());
}
public QuicClientConnectorConfigurator(UnaryOperator<Connection> configurator)
{
this.configurator = Objects.requireNonNull(configurator);
// Initialize to sane defaults for a client.
initQuicConfig.setSessionRecvWindow(16 * 1024 * 1024);
initQuicConfig.setBidirectionalStreamRecvWindow(8 * 1024 * 1024);
initQuicConfig.setDisableActiveMigration(true);
}
public QuicConfiguration getQuicConfiguration()
{
if (!isStarted())
return initQuicConfig;
else
return quicConfig;
}
@Override
protected void doStart() throws Exception
{
ClientConnector clientConnector = getBean(ClientConnector.class);
SslContextFactory.Client sslContextFactory = clientConnector.getSslContextFactory();
quicConfig = new ClientQuicConfiguration(sslContextFactory, initQuicConfig.getPemWorkDirectory());
addBean(quicConfig);
quicConfig.setInputBufferSize(initQuicConfig.getInputBufferSize());
quicConfig.setOutputBufferSize(initQuicConfig.getOutputBufferSize());
quicConfig.setUseInputDirectByteBuffers(initQuicConfig.isUseInputDirectByteBuffers());
quicConfig.setUseOutputDirectByteBuffers(initQuicConfig.isUseOutputDirectByteBuffers());
quicConfig.setProtocols(initQuicConfig.getProtocols());
quicConfig.setDisableActiveMigration(initQuicConfig.isDisableActiveMigration());
quicConfig.setMaxBidirectionalRemoteStreams(initQuicConfig.getMaxBidirectionalRemoteStreams());
quicConfig.setMaxUnidirectionalRemoteStreams(initQuicConfig.getMaxUnidirectionalRemoteStreams());
quicConfig.setSessionRecvWindow(initQuicConfig.getSessionRecvWindow());
quicConfig.setBidirectionalStreamRecvWindow(initQuicConfig.getBidirectionalStreamRecvWindow());
quicConfig.setUnidirectionalStreamRecvWindow(initQuicConfig.getUnidirectionalStreamRecvWindow());
quicConfig.getImplementationConfiguration().putAll(initQuicConfig.getImplementationConfiguration());
super.doStart();
}
@Override
public Transport newTransport()
{
return new QuicTransport(quicConfig);
}
@Override
public boolean isIntrinsicallySecure(ClientConnector clientConnector, SocketAddress address)
{
return true;
}
@Override
public ChannelWithAddress newChannelWithAddress(ClientConnector clientConnector, SocketAddress address, Map<String, Object> context) throws IOException
{
context.put(QuicConfiguration.CONTEXT_KEY, initQuicConfig);
DatagramChannel channel = DatagramChannel.open();
if (clientConnector.getBindAddress() == null)
{
// QUIC must know the local address for connection migration, so we must always bind early.
channel.bind(null);
}
return new ChannelWithAddress(channel, address);
}
@Override
public EndPoint newEndPoint(ClientConnector clientConnector, SocketAddress address, SelectableChannel selectable, ManagedSelector selector, SelectionKey selectionKey)
{
return new DatagramChannelEndPoint((DatagramChannel)selectable, selector, selectionKey, clientConnector.getScheduler());
}
@Override
public Connection newConnection(ClientConnector clientConnector, SocketAddress address, EndPoint endPoint, Map<String, Object> context)
{
return configurator.apply(new ClientQuicConnection(clientConnector, endPoint, context));
}
}

View File

@ -30,6 +30,7 @@ import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.quic.server.QuicServerConnector;
import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
@ -65,6 +66,7 @@ public class End2EndClientTest
</body>
</html>
""";
private QuicTransport transport;
@BeforeEach
public void setUp() throws Exception
@ -83,9 +85,8 @@ public class End2EndClientTest
HttpConfiguration httpConfiguration = new HttpConfiguration();
HttpConnectionFactory http1 = new HttpConnectionFactory(httpConfiguration);
HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(httpConfiguration);
// Use the deprecated APIs for backwards compatibility testing.
connector = new QuicServerConnector(server, sslContextFactory, http1, http2);
connector.getQuicConfiguration().setPemWorkDirectory(workDir.getEmptyPathDir());
ServerQuicConfiguration serverQuicConfiguration = new ServerQuicConfiguration(sslContextFactory, workDir.getEmptyPathDir());
connector = new QuicServerConnector(server, serverQuicConfiguration, http1, http2);
server.addConnector(connector);
server.setHandler(new Handler.Abstract()
@ -100,9 +101,9 @@ public class End2EndClientTest
server.start();
// Use the deprecated APIs for backwards compatibility testing.
ClientConnector clientConnector = new ClientConnector(new QuicClientConnectorConfigurator());
SslContextFactory.Client clientSslContextFactory = new SslContextFactory.Client(true);
transport = new QuicTransport(new ClientQuicConfiguration(clientSslContextFactory, null));
ClientConnector clientConnector = new ClientConnector();
clientConnector.setSslContextFactory(clientSslContextFactory);
ClientConnectionFactory.Info http1Info = HttpClientConnectionFactory.HTTP11;
ClientConnectionFactoryOverHTTP2.HTTP2 http2Info = new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector));
@ -123,6 +124,7 @@ public class End2EndClientTest
public void testSimpleHTTP1() throws Exception
{
ContentResponse response = client.newRequest("https://localhost:" + connector.getLocalPort())
.transport(transport)
.timeout(5, TimeUnit.SECONDS)
.send();
assertThat(response.getStatus(), is(200));
@ -135,6 +137,7 @@ public class End2EndClientTest
{
ContentResponse response = client.newRequest("https://localhost:" + connector.getLocalPort())
.version(HttpVersion.HTTP_2)
.transport(transport)
.timeout(5, TimeUnit.SECONDS)
.send();
assertThat(response.getStatus(), is(200));
@ -148,6 +151,7 @@ public class End2EndClientTest
for (int i = 0; i < 1000; i++)
{
ContentResponse response = client.newRequest("https://localhost:" + connector.getLocalPort() + "/" + i)
.transport(transport)
.timeout(5, TimeUnit.SECONDS)
.send();
assertThat(response.getStatus(), is(200));
@ -169,7 +173,9 @@ public class End2EndClientTest
{
try
{
ContentResponse response = client.GET("https://localhost:" + connector.getLocalPort() + path);
ContentResponse response = client.newRequest("https://localhost:" + connector.getLocalPort() + path)
.transport(transport)
.send();
assertThat(response.getStatus(), is(200));
String contentAsString = response.getContentAsString();
assertThat(contentAsString, is(responseContent));
@ -178,7 +184,7 @@ public class End2EndClientTest
{
throw new RuntimeException(e);
}
});
}, client.getExecutor());
}
CompletableFuture.allOf(futures)
.orTimeout(15, TimeUnit.SECONDS)

View File

@ -30,7 +30,9 @@ import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.Transport;
import org.eclipse.jetty.quic.server.QuicServerConnector;
import org.eclipse.jetty.quic.server.ServerQuicConfiguration;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
@ -67,6 +69,7 @@ public class End2EndClientWithClientCertAuthTest
\t</body>
</html>""";
private SslContextFactory.Server serverSslContextFactory;
private Transport transport;
@BeforeEach
public void setUp() throws Exception
@ -95,9 +98,8 @@ public class End2EndClientWithClientCertAuthTest
httpConfiguration.addCustomizer(new SecureRequestCustomizer());
HttpConnectionFactory http1 = new HttpConnectionFactory(httpConfiguration);
HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(httpConfiguration);
// Use the deprecated APIs for backwards compatibility testing.
connector = new QuicServerConnector(server, serverSslContextFactory, http1, http2);
connector.getQuicConfiguration().setPemWorkDirectory(serverWorkPath);
ServerQuicConfiguration serverQuicConfiguration = new ServerQuicConfiguration(serverSslContextFactory, serverWorkPath);
connector = new QuicServerConnector(server, serverQuicConfiguration, http1, http2);
server.addConnector(connector);
server.setHandler(new Handler.Abstract()
@ -112,15 +114,16 @@ public class End2EndClientWithClientCertAuthTest
server.start();
// Use the deprecated APIs for backwards compatibility testing.
QuicClientConnectorConfigurator configurator = new QuicClientConnectorConfigurator();
configurator.getQuicConfiguration().setPemWorkDirectory(clientWorkPath);
ClientConnector clientConnector = new ClientConnector(configurator);
SslContextFactory.Client clientSslContextFactory = new SslContextFactory.Client();
clientSslContextFactory.setCertAlias("mykey");
clientSslContextFactory.setKeyStore(keyStore);
clientSslContextFactory.setKeyStorePassword("storepwd");
clientSslContextFactory.setTrustStore(keyStore);
clientSslContextFactory.setTrustStorePassword("storepwd");
ClientQuicConfiguration clientQuicConfiguration = new ClientQuicConfiguration(clientSslContextFactory, clientWorkPath);
clientQuicConfiguration.start();
transport = new QuicTransport(clientQuicConfiguration);
ClientConnector clientConnector = new ClientConnector();
clientConnector.setSslContextFactory(clientSslContextFactory);
ClientConnectionFactory.Info http1Info = HttpClientConnectionFactory.HTTP11;
ClientConnectionFactoryOverHTTP2.HTTP2 http2Info = new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector));
@ -141,6 +144,7 @@ public class End2EndClientWithClientCertAuthTest
{
ContentResponse response = client.newRequest("https://localhost:" + connector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.transport(transport)
.send();
assertThat(response.getStatus(), is(200));
String contentAsString = response.getContentAsString();
@ -150,12 +154,13 @@ public class End2EndClientWithClientCertAuthTest
@Test
public void testServerRejectsClientInvalidCert() throws Exception
{
// remove the trust store config from the server
// Remove the trust store config from the server.
server.stop();
serverSslContextFactory.setTrustStore(null);
server.start();
assertThrows(TimeoutException.class, () -> client.newRequest("https://localhost:" + connector.getLocalPort())
.transport(transport)
.timeout(1, TimeUnit.SECONDS)
.send());
}

View File

@ -377,7 +377,7 @@ public abstract class QuicConnection extends AbstractConnection
}
@Override
protected void onCompleteFailure(Throwable cause)
protected void onFailure(Throwable cause)
{
entry.callback.failed(cause);
QuicConnection.this.close();

View File

@ -38,6 +38,7 @@ import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.CyclicTimeout;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.Retainable;
import org.eclipse.jetty.io.RetainableByteBuffer;
import org.eclipse.jetty.quic.quiche.QuicheConnection;
import org.eclipse.jetty.quic.quiche.QuicheConnectionId;
@ -533,7 +534,7 @@ public abstract class QuicSession extends ContainerLifeCycle
{
if (LOG.isDebugEnabled())
LOG.debug("written cipher bytes on {}", QuicSession.this);
cipherBuffer.release();
cipherBuffer = Retainable.release(cipherBuffer);
}
@Override
@ -547,23 +548,25 @@ public abstract class QuicSession extends ContainerLifeCycle
{
if (LOG.isDebugEnabled())
LOG.debug("connection closed {}", QuicSession.this);
finish(new ClosedChannelException());
cipherBuffer = Retainable.release(cipherBuffer);
finishOutwardClose(new ClosedChannelException());
timeout.destroy();
}
@Override
protected void onCompleteFailure(Throwable failure)
protected void onFailure(Throwable failure)
{
if (LOG.isDebugEnabled())
LOG.debug("failed to write cipher bytes, closing session on {}", QuicSession.this, failure);
finish(failure);
}
private void finish(Throwable failure)
{
cipherBuffer.release();
finishOutwardClose(failure);
timeout.destroy();
}
@Override
protected void onCompleteFailure(Throwable cause)
{
cipherBuffer = Retainable.release(cipherBuffer);
}
}
/**

View File

@ -37,7 +37,6 @@ import org.eclipse.jetty.server.AbstractNetworkConnector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.Scheduler;
/**
@ -57,38 +56,11 @@ public class QuicServerConnector extends AbstractNetworkConnector
private volatile DatagramChannel datagramChannel;
private volatile int localPort = -1;
/**
* @param server the {@link Server}
* @param sslContextFactory the {@link SslContextFactory.Server}
* @param factories the {@link ConnectionFactory}s of the protocols transported by QUIC
* @deprecated use {@link #QuicServerConnector(Server, ServerQuicConfiguration, ConnectionFactory...)} instead
*/
@Deprecated(since = "12.0.7", forRemoval = true)
public QuicServerConnector(Server server, SslContextFactory.Server sslContextFactory, ConnectionFactory... factories)
{
this(server, new ServerQuicConfiguration(sslContextFactory, null), factories);
}
public QuicServerConnector(Server server, ServerQuicConfiguration quicConfiguration, ConnectionFactory... factories)
{
this(server, null, null, null, quicConfiguration, factories);
}
/**
* @param server the {@link Server}
* @param executor the {@link Executor}
* @param scheduler the {@link Scheduler}
* @param bufferPool the {@link ByteBufferPool}
* @param sslContextFactory the {@link SslContextFactory.Server}
* @param factories the {@link ConnectionFactory}s of the protocols transported by QUIC
* @deprecated use {@link #QuicServerConnector(Server, Executor, Scheduler, ByteBufferPool, ServerQuicConfiguration, ConnectionFactory...)} instead
*/
@Deprecated(since = "12.0.7", forRemoval = true)
public QuicServerConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool bufferPool, SslContextFactory.Server sslContextFactory, ConnectionFactory... factories)
{
this(server, executor, scheduler, bufferPool, new ServerQuicConfiguration(sslContextFactory, null), factories);
}
public QuicServerConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool bufferPool, ServerQuicConfiguration quicConfiguration, ConnectionFactory... factories)
{
super(server, executor, scheduler, bufferPool, 0, factories);

Some files were not shown because too many files have changed in this diff Show More