Merge remote-tracking branch 'origin/jetty-12.1.x' into jetty-12.1.x-servletUpgrade
This commit is contained in:
commit
594a65099c
|
@ -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.
|
||||
|
|
|
@ -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?**
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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[]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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[]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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>
|
|
@ -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`
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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[]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
====
|
||||
|
|
|
@ -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>
|
||||
----
|
|
@ -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">
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
*
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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" -->
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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[]
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue