Merge remote-tracking branch 'origin/jetty-12.1.x' into experiment/jetty-12.1.x/delayedDispatch
# Conflicts: # jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java # jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTest.java # jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ServletTest.java # jetty-ee11/jetty-ee11-servlet/src/test/java/org/eclipse/jetty/ee11/servlet/ServletTest.java # jetty-ee9/jetty-ee9-servlet/src/test/java/org/eclipse/jetty/ee9/servlet/ServletTest.java
This commit is contained in:
commit
b7c8bcb92f
|
@ -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.
|
||||
|
|
|
@ -18,7 +18,7 @@ pipeline {
|
|||
stage("Build / Test - JDK21") {
|
||||
agent { node { label 'linux' } }
|
||||
steps {
|
||||
timeout( time: 180, unit: 'MINUTES' ) {
|
||||
timeout( time: 210, unit: 'MINUTES' ) {
|
||||
checkout scm
|
||||
mavenBuild( "jdk21", "clean install -Dspotbugs.skip=true -Djacoco.skip=true", "maven3")
|
||||
recordIssues id: "jdk21", name: "Static Analysis jdk21", aggregatingResults: true, enabledForFailure: true,
|
||||
|
@ -31,7 +31,7 @@ pipeline {
|
|||
stage("Build / Test - JDK22") {
|
||||
agent { node { label 'linux' } }
|
||||
steps {
|
||||
timeout( time: 180, unit: 'MINUTES' ) {
|
||||
timeout( time: 210, unit: 'MINUTES' ) {
|
||||
checkout scm
|
||||
mavenBuild( "jdk22", "clean install -Dspotbugs.skip=true -Djacoco.skip=true", "maven3")
|
||||
recordIssues id: "jdk22", name: "Static Analysis jdk22", aggregatingResults: true, enabledForFailure: true, tools: [mavenConsole(), java(), checkStyle(), javaDoc()]
|
||||
|
@ -39,10 +39,21 @@ pipeline {
|
|||
}
|
||||
}
|
||||
|
||||
stage("Build / Test - JDK17") {
|
||||
stage("Build / Test - JDK23") {
|
||||
agent { node { label 'linux' } }
|
||||
steps {
|
||||
timeout( time: 180, unit: 'MINUTES' ) {
|
||||
checkout scm
|
||||
mavenBuild( "jdk23", "clean install -Dspotbugs.skip=true -Djacoco.skip=true", "maven3")
|
||||
recordIssues id: "jdk23", name: "Static Analysis jdk23", aggregatingResults: true, enabledForFailure: true, tools: [mavenConsole(), java(), checkStyle(), javaDoc()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("Build / Test - JDK17") {
|
||||
agent { node { label 'linux' } }
|
||||
steps {
|
||||
timeout( time: 210, unit: 'MINUTES' ) {
|
||||
checkout scm
|
||||
mavenBuild( "jdk17", "clean install -Perrorprone", "maven3") // javadoc:javadoc
|
||||
recordIssues id: "analysis-jdk17", name: "Static Analysis jdk17", aggregatingResults: true, enabledForFailure: true,
|
||||
|
@ -114,11 +125,11 @@ def mavenBuild(jdk, cmdline, mvnName) {
|
|||
buildCache = useBuildCache()
|
||||
if (buildCache) {
|
||||
echo "Using build cache"
|
||||
extraArgs = " -Dmaven.build.cache.restoreGeneratedSources=false -Dmaven.build.cache.remote.url=http://nginx-cache-service.jenkins.svc.cluster.local:80 -Dmaven.build.cache.remote.enabled=true -Dmaven.build.cache.remote.save.enabled=true -Dmaven.build.cache.remote.server.id=remote-build-cache-server -Daether.connector.http.supportWebDav=true "
|
||||
extraArgs = " -Dmaven.build.cache.restoreGeneratedSources=false -Dmaven.build.cache.remote.url=http://nexus-service.nexus.svc.cluster.local:8081/repository/maven-build-cache -Dmaven.build.cache.remote.enabled=true -Dmaven.build.cache.remote.save.enabled=true -Dmaven.build.cache.remote.server.id=nexus-cred "
|
||||
} else {
|
||||
// when not using cache
|
||||
echo "Not using build cache"
|
||||
extraArgs = " -Dmaven.test.failure.ignore=true -Dmaven.build.cache.skipCache=true -Dmaven.build.cache.remote.url=http://nginx-cache-service.jenkins.svc.cluster.local:80 -Dmaven.build.cache.remote.enabled=true -Dmaven.build.cache.remote.save.enabled=true -Dmaven.build.cache.remote.server.id=remote-build-cache-server -Daether.connector.http.supportWebDav=true "
|
||||
extraArgs = " -Dmaven.test.failure.ignore=true -Dmaven.build.cache.skipCache=true -Dmaven.build.cache.remote.url=http://nexus-service.nexus.svc.cluster.local:8081/repository/maven-build-cache -Dmaven.build.cache.remote.enabled=true -Dmaven.build.cache.remote.save.enabled=true -Dmaven.build.cache.remote.server.id=nexus-cred "
|
||||
}
|
||||
if (env.BRANCH_NAME ==~ /PR-\d+/) {
|
||||
if (pullRequest.labels.contains("build-all-tests")) {
|
||||
|
|
|
@ -17,11 +17,9 @@ nav:
|
|||
- modules/programming-guide/nav.adoc
|
||||
ext:
|
||||
collector:
|
||||
- run:
|
||||
command: mvn install -ntp -B -Dcollector -Pfast -am -pl documentation/jetty
|
||||
scan:
|
||||
dir: documentation/jetty/target/collector
|
||||
- run: mvn install -ntp -B -Dcollector -Pfast -am -pl documentation/jetty
|
||||
scan: ./target/collector
|
||||
- scan:
|
||||
dir: jetty-core/jetty-server/src/main/java
|
||||
files: org/eclipse/jetty/server/CustomRequestLog.java
|
||||
base: modules/code/partials
|
||||
into: modules/code/partials
|
||||
|
|
|
@ -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;
|
||||
|
@ -1049,6 +1052,31 @@ public class HTTPClientDocs
|
|||
// end::setConnectionPool[]
|
||||
}
|
||||
|
||||
public void preCreateConnections() throws Exception
|
||||
{
|
||||
// tag::preCreateConnections[]
|
||||
HttpClient httpClient = new HttpClient();
|
||||
httpClient.start();
|
||||
|
||||
// For HTTP/1.1, you need to explicitly configure to initialize connections.
|
||||
if (httpClient.getTransport() instanceof HttpClientTransportOverHTTP http1)
|
||||
http1.setInitializeConnections(true);
|
||||
|
||||
// Create a dummy request to the server you want to pre-create connections to.
|
||||
Request request = httpClient.newRequest("https://host/");
|
||||
|
||||
// Resolve the destination for that request.
|
||||
Destination destination = httpClient.resolveDestination(request);
|
||||
|
||||
// Pre-create, for example, half of the connections.
|
||||
int preCreate = httpClient.getMaxConnectionsPerDestination() / 2;
|
||||
CompletableFuture<Void> completable = destination.getConnectionPool().preCreateConnections(preCreate);
|
||||
|
||||
// Wait for the connections to be created.
|
||||
completable.get(5, TimeUnit.SECONDS);
|
||||
// end::preCreateConnections[]
|
||||
}
|
||||
|
||||
public void unixDomain() throws Exception
|
||||
{
|
||||
// tag::unixDomain[]
|
||||
|
@ -1155,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,43 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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 org.eclipse.jetty.security.Constraint;
|
||||
import org.eclipse.jetty.security.SecurityHandler;
|
||||
import org.eclipse.jetty.security.siwe.EthereumAuthenticator;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
|
||||
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
|
||||
{
|
||||
|
|
|
@ -95,7 +95,7 @@ public class SessionDocs
|
|||
org.eclipse.jetty.session.SessionHandler sessionHandler = new org.eclipse.jetty.session.SessionHandler();
|
||||
sessionHandler.setSessionCookie("SIMPLE");
|
||||
sessionHandler.setUsingCookies(true);
|
||||
sessionHandler.setUsingURLs(false);
|
||||
sessionHandler.setUsingUriParameters(false);
|
||||
sessionHandler.setSessionPath("/");
|
||||
server.setHandler(sessionHandler);
|
||||
sessionHandler.setHandler(new Handler.Abstract()
|
||||
|
|
|
@ -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,9 @@
|
|||
** 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[]
|
||||
** xref:migration/12.0-to-12.1.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.
|
||||
|
|
|
@ -158,7 +158,7 @@ Jetty's client library provides the following `ConnectionPool` implementations:
|
|||
* `DuplexConnectionPool`, historically the first implementation, only used by the HTTP/1.1 transport.
|
||||
* `MultiplexConnectionPool`, the generic implementation valid for any transport where connections are reused with a most recently used algorithm (that is, the connections most recently returned to the connection pool are the more likely to be used again).
|
||||
* `RoundRobinConnectionPool`, similar to `MultiplexConnectionPool` but where connections are reused with a round-robin algorithm.
|
||||
* `RandomRobinConnectionPool`, similar to `MultiplexConnectionPool` but where connections are reused with an algorithm that chooses them randomly.
|
||||
* `RandomConnectionPool`, similar to `MultiplexConnectionPool` but where connections are reused with an algorithm that chooses them randomly.
|
||||
|
||||
The `ConnectionPool` implementation can be customized for each destination in by setting a `ConnectionPool.Factory` on the `HttpClientTransport`:
|
||||
|
||||
|
@ -167,6 +167,34 @@ The `ConnectionPool` implementation can be customized for each destination in by
|
|||
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tags=setConnectionPool]
|
||||
----
|
||||
|
||||
[[connection-pool-precreate-connections]]
|
||||
=== Pre-Creating Connections
|
||||
|
||||
`ConnectionPool` offers the ability to pre-create connections by calling `ConnectionPool.preCreateConnections(int)`.
|
||||
|
||||
Pre-creating the connections saves the time and processing spent to establish the TCP connection, performing the TLS handshake (if necessary) and, for HTTP/2 and HTTP/3, perform the initial protocol setup.
|
||||
This is particularly important for HTTP/2 because in the initial protocol setup the server informs the client of the maximum number of concurrent requests per connection (otherwise assumed to be just `1` by the client).
|
||||
|
||||
The scenarios where pre-creating connections is useful are, for example:
|
||||
|
||||
* Load testing, where you want to prepare the system with connections already created to avoid paying of cost of connection setup.
|
||||
* Proxying scenarios, often in conjunction with the use of `RoundRobinConnectionPool` or `RandomConnectionPool`, where the proxy creates early the connections to the backend servers.
|
||||
|
||||
This is an example of how to pre-create connections:
|
||||
|
||||
[,java,indent=0]
|
||||
----
|
||||
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tags=preCreateConnections]
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Pre-creating connections for secure HTTP/1.1 requires you to call `HttpClientTransportOverHTTP.setInitializeConnections(true)`, otherwise only the TCP connection is established, but the TLS handshake is not initiated.
|
||||
|
||||
To initialize connections for secure HTTP/1.1, the client sends an initial `OPTIONS * HTTP/1.1` request to the server.
|
||||
The server must be able to handle this request without closing the connection (in particular it must not add the `Connection: close` header in the response).
|
||||
====
|
||||
|
||||
[[request-processing]]
|
||||
== HttpClient Request Processing
|
||||
|
||||
|
@ -207,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.
|
||||
|
@ -500,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.
|
||||
====
|
||||
|
|
|
@ -99,6 +99,15 @@
|
|||
| `org.eclipse.jetty.websocket.api.**WebSocketPolicy**` | `org.eclipse.jetty.websocket.api.**Configurable**`
|
||||
|===
|
||||
|
||||
== Server-Side Web Application APIs Changes
|
||||
|
||||
Jetty 12 introduced redesigned server-side APIs for web applications.
|
||||
In Jetty 11, these APIs were based on a mix of Jakarta Servlet APIs and Jetty Handler APIs, while in Jetty 12 they are solely based on Jetty Handler APIs.
|
||||
|
||||
In Jetty 12 you can now write web applications independently of the Servlet APIs, so you can migrate Jakarta Servlets to Jetty Handlers as explained in xref:servlet-to-handler[this section].
|
||||
|
||||
If you were already using the Jetty 11 Handler APIs, you can migrate them to the Jetty 12 Handler APIs as explained in xref:api-changes-handler[this section].
|
||||
|
||||
[[servlet-to-handler]]
|
||||
== Migrate Servlets to Jetty Handlers
|
||||
|
||||
|
@ -126,6 +135,8 @@ include::code:example$src/main/java/org/eclipse/jetty/docs/programming/migration
|
|||
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=requestContent-source]
|
||||
----
|
||||
|
||||
Refer also to the `Content.Source` APIs detailed in xref:arch/io.adoc#content-source[this section].
|
||||
|
||||
=== Handler Response APIs
|
||||
[,java,indent=0]
|
||||
----
|
||||
|
@ -150,9 +161,48 @@ include::code:example$src/main/java/org/eclipse/jetty/docs/programming/migration
|
|||
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=responseContent-trailers]
|
||||
----
|
||||
|
||||
Refer also to the `Content.Sink` APIs detailed in xref:arch/io.adoc#content-sink[this section].
|
||||
|
||||
[[api-changes]]
|
||||
== APIs Changes
|
||||
|
||||
[[api-changes-handler]]
|
||||
=== `Handler`
|
||||
|
||||
The server-side `Handler` class, and the APIs to use for request/response processing, have been redesigned in Jetty 12.
|
||||
|
||||
The Jetty 11 `Handler` method:
|
||||
|
||||
`Handler.handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)`
|
||||
|
||||
has been changed in Jetty 12 to:
|
||||
|
||||
`Handler.handle(Request request, Response response, Callback callback)`
|
||||
|
||||
The Jetty 11 `target` parameter has been removed, and in Jetty 12 it has been replaced by the information present in `Request.getHttpURI()`.
|
||||
|
||||
In Jetty 11, ``Handler``s would mark the fact that they handled the request, and therefore are producing a response, by calling `Request.setHandled(true)`.
|
||||
In Jetty 12, this is performed by returning `true` from the `Handler.handle(\...)` method, which also requires that the `Callback` parameter must be completed, either by succeeding it or failing it.
|
||||
|
||||
In Jetty 11, the `Handler.handle(\...)` method has a blocking semantic, while in Jetty 12 the `Handler.handle(\...)` method has an asynchronous semantic, thanks to the `Callback` parameter.
|
||||
This means that you can return from the `Handler.handle(\...)` method _before_ the response has been sent, similarly to what you can do with the Servlet APIs when you call `HttpServletRequest.startAsync()`.
|
||||
Similarly, in Jetty 11 after a call to `startAsync()` you must call `AsyncContext.complete()`, while in Jetty 12 you must complete the `Callback` parameter, either by succeeding it or failing it.
|
||||
|
||||
In Jetty 11, `AbstractHandler` provides a utility class to implement `Handler`.
|
||||
In Jetty 12, use `Handler.Abstract`.
|
||||
|
||||
In Jetty 11, the APIs to deal with request or response HTTP headers are based on either Jetty's `HttpFields`, or the Servlet APIs.
|
||||
In Jetty 12, the HTTP headers API are only based on `HttpFields`.
|
||||
Please refer to the `HttpFields` link:{javadoc-url}/org/eclipse/jetty/http/HttpFields.html[javadocs] for details.
|
||||
|
||||
In Jetty 11, the request content is accessed via `Request.getInputStream()` or `HttpServletRequest.getInputStream()`.
|
||||
In Jetty 12, the `Request` object itself _is-a_ `Content.Source` that can be read as explained in xref:arch/io.adoc#content-source[this section].
|
||||
In Jetty 12, you can use `Content.Source.asInputStream(request)` to obtain an `InputStream` and minimize the changes to your code, but remember that `InputStream` only provides blocking APIs, while `Content.Source` provides non-blocking APIs.
|
||||
|
||||
In Jetty 11, the response content is accessed via `Response.getOutputStream()` or `HttpServletResponse.getOutputStream()`.
|
||||
In Jetty 12, the `Response` object itself _is-a_ `Content.Sink` that can be written as explained in xref:arch/io.adoc#content-sink[this section].
|
||||
In Jetty 12, you can use `Content.Sink.asOutputStream(response)` to obtain an `OutputStream` and minimize the changes to your code, but remember that `OutputStream` only provides blocking APIs, while `Content.Sink` provides non-blocking APIs.
|
||||
|
||||
=== `HttpClient`
|
||||
|
||||
The Jetty 11 `Request.onResponseContentDemanded(Response.DemandedContentListener)` API has been replaced by `Request.onResponseContentSource(Response.ContentSourceListener)` in Jetty 12.
|
||||
|
@ -165,6 +215,8 @@ The Jetty 12 model is a "demand+pull" model: when the content is available, the
|
|||
|
||||
For more information about the new model, see xref:arch/io.adoc#content-source[this section].
|
||||
|
||||
Jetty 12 introduced the concept of low-level transport for high-level protocols, described in xref:client/io-arch.adoc#transport[this section].
|
||||
|
||||
=== WebSocket
|
||||
|
||||
The Jetty WebSocket APIs have been vastly simplified, and brought in line with the style of other APIs.
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
= Migrating from Jetty 12.0.x to Jetty 12.1.x
|
||||
|
||||
[[api-changes]]
|
||||
== APIs Changes
|
||||
|
||||
=== `IteratingCallback`
|
||||
|
||||
Class `IteratingCallback` underwent refinements that changed the behavior of the `onCompleteFailure(Throwable)` method.
|
||||
|
||||
In Jetty 12.0.x, `IteratingCallback.onCompleteFailure(Throwable)` was called as soon as a failure was reported, without waiting the completion of the asynchronous operation (despite its name containing the word "complete").
|
||||
|
||||
For example, if a write operation performed with `IteratingCallback` was pending due to TCP congestion, and a timeout happened, `onCompleteFailure(Throwable)` was called as soon as the timeout happened, without waiting for the TCP congestion to resolve.
|
||||
|
||||
In Jetty 12.1.x, the same behavior is achieved by `IteratingCallback.onFailure(Throwable)`, so applications should review their usage of `IteratingCallback` and change the overrides of `onCompleteFailure(Throwable)` to override `onFailure(Throwable)` instead.
|
|
@ -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>
|
||||
----
|
|
@ -57,8 +57,7 @@ public class ConscryptHTTP2ClientTest
|
|||
sslContextFactory.setProvider("Conscrypt");
|
||||
Conscrypt.setDefaultHostnameVerifier((certs, hostname, session) -> true);
|
||||
|
||||
HTTP2Client client = new HTTP2Client();
|
||||
try
|
||||
try (HTTP2Client client = new HTTP2Client())
|
||||
{
|
||||
client.addBean(sslContextFactory);
|
||||
client.start();
|
||||
|
@ -97,10 +96,6 @@ public class ConscryptHTTP2ClientTest
|
|||
|
||||
assertTrue(latch.await(15, TimeUnit.SECONDS));
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canConnectTo(String host, int port)
|
||||
|
|
|
@ -132,17 +132,12 @@ public class ConscryptHTTP2ServerTest
|
|||
ClientConnector clientConnector = new ClientConnector();
|
||||
clientConnector.setSslContextFactory(newClientSslContextFactory());
|
||||
HTTP2Client h2Client = new HTTP2Client(clientConnector);
|
||||
HttpClient client = new HttpClient(new HttpClientTransportOverHTTP2(h2Client));
|
||||
client.start();
|
||||
try
|
||||
try (HttpClient client = new HttpClient(new HttpClientTransportOverHTTP2(h2Client)))
|
||||
{
|
||||
client.start();
|
||||
int port = ((ServerConnector)server.getConnectors()[0]).getLocalPort();
|
||||
ContentResponse contentResponse = client.GET("https://localhost:" + port);
|
||||
assertEquals(200, contentResponse.getStatus());
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,8 +45,7 @@ public class JDK9HTTP2ClientTest
|
|||
|
||||
Assumptions.assumeTrue(canConnectTo(host, port));
|
||||
|
||||
HTTP2Client client = new HTTP2Client();
|
||||
try
|
||||
try (HTTP2Client client = new HTTP2Client())
|
||||
{
|
||||
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
|
||||
client.addBean(sslContextFactory);
|
||||
|
@ -87,10 +86,6 @@ public class JDK9HTTP2ClientTest
|
|||
|
||||
latch.await(15, TimeUnit.SECONDS);
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canConnectTo(String host, int port)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,7 +127,8 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
|
|||
{
|
||||
// The request may still be sending content, stop it.
|
||||
Request request = response.getRequest();
|
||||
request.abort(new HttpRequestException("Aborting request after receiving a %d response".formatted(response.getStatus()), request));
|
||||
if (request.getBody() != null)
|
||||
request.abort(new HttpRequestException("Aborting request after receiving a %d response".formatted(response.getStatus()), request));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,10 +39,14 @@ public class ContinueProtocolHandler implements ProtocolHandler
|
|||
@Override
|
||||
public boolean accept(Request request, Response response)
|
||||
{
|
||||
boolean is100 = response.getStatus() == HttpStatus.CONTINUE_100;
|
||||
boolean expect100 = request.getHeaders().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
|
||||
boolean handled100 = request.getAttributes().containsKey(ATTRIBUTE);
|
||||
return (is100 || expect100) && !handled100;
|
||||
if (handled100)
|
||||
return false;
|
||||
boolean is100 = response.getStatus() == HttpStatus.CONTINUE_100;
|
||||
if (is100)
|
||||
return true;
|
||||
// Also handle non-100 responses, because we need to complete the request to complete the whole exchange.
|
||||
return request.getHeaders().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -20,13 +20,14 @@ import org.eclipse.jetty.http.HttpField;
|
|||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.RetainableByteBuffer;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
|
||||
/**
|
||||
* {@link ContentDecoder} for the "gzip" encoding.
|
||||
*/
|
||||
public class GZIPContentDecoder extends org.eclipse.jetty.http.GZIPContentDecoder implements ContentDecoder
|
||||
{
|
||||
public static final int DEFAULT_BUFFER_SIZE = 8192;
|
||||
public static final int DEFAULT_BUFFER_SIZE = IO.DEFAULT_BUFFER_SIZE;
|
||||
|
||||
private long decodedLength;
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ import org.slf4j.LoggerFactory;
|
|||
* }</pre>
|
||||
*/
|
||||
@ManagedObject("The HTTP client")
|
||||
public class HttpClient extends ContainerLifeCycle
|
||||
public class HttpClient extends ContainerLifeCycle implements AutoCloseable
|
||||
{
|
||||
public static final String USER_AGENT = "Jetty/" + Jetty.VERSION;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpClient.class);
|
||||
|
@ -471,13 +471,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
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);
|
||||
}
|
||||
|
||||
|
@ -1141,4 +1135,10 @@ public class HttpClient extends ContainerLifeCycle
|
|||
sslContextFactory = getSslContextFactory();
|
||||
return new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), connectionFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception
|
||||
{
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -61,7 +61,8 @@ public class RedirectProtocolHandler implements ProtocolHandler, Response.Listen
|
|||
{
|
||||
// The request may still be sending content, stop it.
|
||||
Request request = response.getRequest();
|
||||
request.abort(new HttpRequestException("Aborting request after receiving a %d response".formatted(response.getStatus()), request));
|
||||
if (request.getBody() != null)
|
||||
request.abort(new HttpRequestException("Aborting request after receiving a %d response".formatted(response.getStatus()), request));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -26,20 +26,49 @@ public class HttpClientConnectionFactory implements ClientConnectionFactory
|
|||
/**
|
||||
* <p>Representation of the {@code HTTP/1.1} application protocol used by {@link HttpClientTransportDynamic}.</p>
|
||||
*/
|
||||
public static final Info HTTP11 = new HTTP11(new HttpClientConnectionFactory());
|
||||
public static final Info HTTP11 = new HTTP11();
|
||||
|
||||
private boolean initializeConnections;
|
||||
|
||||
/**
|
||||
* @return whether newly created connections should be initialized with an {@code OPTIONS * HTTP/1.1} request
|
||||
*/
|
||||
public boolean isInitializeConnections()
|
||||
{
|
||||
return initializeConnections;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param initialize whether newly created connections should be initialized with an {@code OPTIONS * HTTP/1.1} request
|
||||
*/
|
||||
public void setInitializeConnections(boolean initialize)
|
||||
{
|
||||
this.initializeConnections = initialize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context)
|
||||
{
|
||||
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, context);
|
||||
connection.setInitialize(isInitializeConnections());
|
||||
return customize(connection, context);
|
||||
}
|
||||
|
||||
private static class HTTP11 extends Info
|
||||
/**
|
||||
* <p>Representation of the {@code HTTP/1.1} application protocol used by {@link HttpClientTransportDynamic}.</p>
|
||||
* <p>Applications should prefer using the constant {@link HttpClientConnectionFactory#HTTP11}, unless they
|
||||
* need to customize the associated {@link HttpClientConnectionFactory}.</p>
|
||||
*/
|
||||
public static class HTTP11 extends Info
|
||||
{
|
||||
private static final List<String> protocols = List.of("http/1.1");
|
||||
|
||||
private HTTP11(ClientConnectionFactory factory)
|
||||
public HTTP11()
|
||||
{
|
||||
this(new HttpClientConnectionFactory());
|
||||
}
|
||||
|
||||
public HTTP11(ClientConnectionFactory factory)
|
||||
{
|
||||
super(factory);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ import org.eclipse.jetty.client.Destination;
|
|||
import org.eclipse.jetty.client.DuplexConnectionPool;
|
||||
import org.eclipse.jetty.client.Origin;
|
||||
import org.eclipse.jetty.client.Request;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.ProcessorUtils;
|
||||
|
@ -37,7 +36,7 @@ public class HttpClientTransportOverHTTP extends AbstractConnectorHttpClientTran
|
|||
public static final Origin.Protocol HTTP11 = new Origin.Protocol(List.of("http/1.1"), false);
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpClientTransportOverHTTP.class);
|
||||
|
||||
private final ClientConnectionFactory factory = new HttpClientConnectionFactory();
|
||||
private final HttpClientConnectionFactory factory = new HttpClientConnectionFactory();
|
||||
private int headerCacheSize = 1024;
|
||||
private boolean headerCacheCaseSensitive;
|
||||
|
||||
|
@ -79,25 +78,54 @@ public class HttpClientTransportOverHTTP extends AbstractConnectorHttpClientTran
|
|||
return connection;
|
||||
}
|
||||
|
||||
@ManagedAttribute("The maximum allowed size in bytes for an HTTP header field cache")
|
||||
/**
|
||||
* @return the max size in bytes for the HTTP header field cache
|
||||
*/
|
||||
@ManagedAttribute("The maximum allowed size in bytes for the HTTP header field cache")
|
||||
public int getHeaderCacheSize()
|
||||
{
|
||||
return headerCacheSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param headerCacheSize the max size in bytes for the HTTP header field cache
|
||||
*/
|
||||
public void setHeaderCacheSize(int headerCacheSize)
|
||||
{
|
||||
this.headerCacheSize = headerCacheSize;
|
||||
}
|
||||
|
||||
@ManagedAttribute("Whether the header field cache is case sensitive")
|
||||
/**
|
||||
* @return whether the HTTP header field cache is case-sensitive
|
||||
*/
|
||||
@ManagedAttribute("Whether the HTTP header field cache is case-sensitive")
|
||||
public boolean isHeaderCacheCaseSensitive()
|
||||
{
|
||||
return headerCacheCaseSensitive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param headerCacheCaseSensitive whether the HTTP header field cache is case-sensitive
|
||||
*/
|
||||
public void setHeaderCacheCaseSensitive(boolean headerCacheCaseSensitive)
|
||||
{
|
||||
this.headerCacheCaseSensitive = headerCacheCaseSensitive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether newly created connections should be initialized with an {@code OPTIONS * HTTP/1.1} request
|
||||
*/
|
||||
@ManagedAttribute("Whether newly created connections should be initialized with an OPTIONS * HTTP/1.1 request")
|
||||
public boolean isInitializeConnections()
|
||||
{
|
||||
return factory.isInitializeConnections();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param initialize whether newly created connections should be initialized with an {@code OPTIONS * HTTP/1.1} request
|
||||
*/
|
||||
public void setInitializeConnections(boolean initialize)
|
||||
{
|
||||
factory.setInitializeConnections(initialize);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ import java.util.concurrent.TimeoutException;
|
|||
|
||||
import org.eclipse.jetty.client.Authentication;
|
||||
import org.eclipse.jetty.client.AuthenticationStore;
|
||||
import org.eclipse.jetty.client.BytesRequestContent;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpProxy;
|
||||
import org.eclipse.jetty.client.HttpRequestException;
|
||||
|
@ -35,6 +34,7 @@ import org.eclipse.jetty.http.HttpHeader;
|
|||
import org.eclipse.jetty.io.CyclicTimeouts;
|
||||
import org.eclipse.jetty.util.Attachable;
|
||||
import org.eclipse.jetty.util.NanoTime;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -146,7 +146,7 @@ public abstract class HttpConnection implements IConnection, Attachable
|
|||
|
||||
// Make sure the path is there
|
||||
String path = request.getPath();
|
||||
if (path.trim().length() == 0)
|
||||
if (StringUtil.isBlank(path))
|
||||
{
|
||||
path = "/";
|
||||
request.path(path);
|
||||
|
@ -191,11 +191,7 @@ public abstract class HttpConnection implements IConnection, Attachable
|
|||
|
||||
// Add content headers.
|
||||
Request.Content content = request.getBody();
|
||||
if (content == null)
|
||||
{
|
||||
request.body(new BytesRequestContent());
|
||||
}
|
||||
else
|
||||
if (content != null)
|
||||
{
|
||||
if (!headers.contains(HttpHeader.CONTENT_TYPE))
|
||||
{
|
||||
|
@ -203,10 +199,7 @@ public abstract class HttpConnection implements IConnection, Attachable
|
|||
if (contentType == null)
|
||||
contentType = getHttpClient().getDefaultRequestContentType();
|
||||
if (contentType != null)
|
||||
{
|
||||
HttpField field = new HttpField(HttpHeader.CONTENT_TYPE, contentType);
|
||||
request.addHeader(field);
|
||||
}
|
||||
request.addHeader(new HttpField(HttpHeader.CONTENT_TYPE, contentType));
|
||||
}
|
||||
long contentLength = content.getLength();
|
||||
if (contentLength >= 0)
|
||||
|
@ -215,6 +208,9 @@ public abstract class HttpConnection implements IConnection, Attachable
|
|||
request.addHeader(new HttpField.LongValueHttpField(HttpHeader.CONTENT_LENGTH, contentLength));
|
||||
}
|
||||
}
|
||||
// RFC 9110, section 10.1.1.
|
||||
if (content == null || content.getLength() == 0)
|
||||
request.headers(h -> h.remove(HttpHeader.EXPECT));
|
||||
|
||||
// Cookies.
|
||||
StringBuilder cookies = convertCookies(request.getCookies(), null);
|
||||
|
@ -243,7 +239,7 @@ public abstract class HttpConnection implements IConnection, Attachable
|
|||
{
|
||||
if (builder == null)
|
||||
builder = new StringBuilder();
|
||||
if (builder.length() > 0)
|
||||
if (!builder.isEmpty())
|
||||
builder.append("; ");
|
||||
builder.append(cookie.getName()).append("=").append(cookie.getValue());
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
*
|
||||
|
|
|
@ -67,7 +67,7 @@ public abstract class HttpReceiver
|
|||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpReceiver.class);
|
||||
|
||||
private final SerializedInvoker invoker = new SerializedInvoker();
|
||||
private final SerializedInvoker invoker = new SerializedInvoker(HttpReceiver.class);
|
||||
private final HttpChannel channel;
|
||||
private ResponseState responseState = ResponseState.IDLE;
|
||||
private NotifiableContentSource contentSource;
|
||||
|
@ -315,14 +315,25 @@ public abstract class HttpReceiver
|
|||
* Method to be invoked when response content is available to be read.
|
||||
* <p>
|
||||
* This method takes care of ensuring the {@link Content.Source} passed to
|
||||
* {@link Response.ContentSourceListener#onContentSource(Response, Content.Source)} calls the
|
||||
* demand callback.
|
||||
* {@link Response.ContentSourceListener#onContentSource(Response, Content.Source)}
|
||||
* calls the demand callback.
|
||||
* The call to the demand callback is serialized with other events.
|
||||
*/
|
||||
protected void responseContentAvailable()
|
||||
protected void responseContentAvailable(HttpExchange exchange)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Response content available on {}", this);
|
||||
contentSource.onDataAvailable();
|
||||
LOG.debug("Invoking responseContentAvailable on {}", this);
|
||||
|
||||
invoker.run(() ->
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Executing responseContentAvailable on {}", this);
|
||||
|
||||
if (exchange.isResponseCompleteOrTerminated())
|
||||
return;
|
||||
|
||||
contentSource.onDataAvailable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -344,7 +355,7 @@ public abstract class HttpReceiver
|
|||
if (!exchange.responseComplete(null))
|
||||
return;
|
||||
|
||||
invoker.run(() ->
|
||||
Runnable successTask = () ->
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Executing responseSuccess on {}", this);
|
||||
|
@ -365,7 +376,12 @@ public abstract class HttpReceiver
|
|||
// Mark atomically the response as terminated, with
|
||||
// respect to concurrency between request and response.
|
||||
terminateResponse(exchange);
|
||||
}, afterSuccessTask);
|
||||
};
|
||||
|
||||
if (afterSuccessTask == null)
|
||||
invoker.run(successTask);
|
||||
else
|
||||
invoker.run(successTask, afterSuccessTask);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -693,6 +709,9 @@ public abstract class HttpReceiver
|
|||
|
||||
current = HttpReceiver.this.read(false);
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Read {} from {}", current, this);
|
||||
|
||||
try (AutoLock ignored = lock.lock())
|
||||
{
|
||||
if (currentChunk != null)
|
||||
|
@ -712,9 +731,10 @@ public abstract class HttpReceiver
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onDataAvailable on {}", this);
|
||||
// The demandCallback will call read() that will itself call
|
||||
// HttpReceiver.read(boolean) so it must be called by the invoker.
|
||||
invokeDemandCallback(true);
|
||||
invoker.assertCurrentThreadInvoking();
|
||||
// The onDataAvailable() method is only ever called
|
||||
// by the invoker so avoid using the invoker again.
|
||||
invokeDemandCallback(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -736,6 +756,8 @@ public abstract class HttpReceiver
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Processing demand on {}", this);
|
||||
|
||||
invoker.assertCurrentThreadInvoking();
|
||||
|
||||
Content.Chunk current;
|
||||
try (AutoLock ignored = lock.lock())
|
||||
{
|
||||
|
@ -760,8 +782,8 @@ public abstract class HttpReceiver
|
|||
}
|
||||
}
|
||||
|
||||
// The processDemand method is only ever called by the
|
||||
// invoker so there is no need to use the latter here.
|
||||
// The processDemand() method is only ever called
|
||||
// by the invoker so avoid using the invoker again.
|
||||
invokeDemandCallback(false);
|
||||
}
|
||||
|
||||
|
@ -769,21 +791,25 @@ public abstract class HttpReceiver
|
|||
{
|
||||
Runnable demandCallback = demandCallbackRef.getAndSet(null);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Invoking demand callback on {}", this);
|
||||
if (demandCallback != null)
|
||||
LOG.debug("Invoking demand callback {} on {}", demandCallback, this);
|
||||
if (demandCallback == null)
|
||||
return;
|
||||
try
|
||||
{
|
||||
try
|
||||
if (invoke)
|
||||
{
|
||||
if (invoke)
|
||||
invoker.run(demandCallback);
|
||||
else
|
||||
demandCallback.run();
|
||||
invoker.run(demandCallback);
|
||||
}
|
||||
catch (Throwable x)
|
||||
else
|
||||
{
|
||||
fail(x);
|
||||
invoker.assertCurrentThreadInvoking();
|
||||
demandCallback.run();
|
||||
}
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
fail(x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -386,8 +386,11 @@ public abstract class HttpSender
|
|||
}
|
||||
}
|
||||
|
||||
private void internalAbort(HttpExchange exchange, Throwable failure)
|
||||
private void internalAbort(Throwable failure)
|
||||
{
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange == null)
|
||||
return;
|
||||
anyToFailure(failure);
|
||||
abortRequest(exchange);
|
||||
}
|
||||
|
@ -528,7 +531,7 @@ public abstract class HttpSender
|
|||
action.run();
|
||||
|
||||
// Read the request content.
|
||||
chunk = content.read();
|
||||
chunk = content != null ? content.read() : Content.Chunk.EOF;
|
||||
}
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Content {} for {}", chunk, request);
|
||||
|
@ -539,6 +542,7 @@ public abstract class HttpSender
|
|||
{
|
||||
// No content after the headers, demand.
|
||||
demanded = true;
|
||||
assert content != null;
|
||||
content.demand(this::succeeded);
|
||||
return Action.SCHEDULED;
|
||||
}
|
||||
|
@ -616,22 +620,24 @@ 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);
|
||||
internalAbort(x);
|
||||
|
||||
Promise<Boolean> promise = abort;
|
||||
if (promise != null)
|
||||
promise.succeeded(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCompleteFailure(Throwable x)
|
||||
{
|
||||
if (chunk != null)
|
||||
chunk.release();
|
||||
chunk = Content.Chunk.next(chunk);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvocationType getInvocationType()
|
||||
{
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
import org.eclipse.jetty.client.Connection;
|
||||
import org.eclipse.jetty.client.Destination;
|
||||
import org.eclipse.jetty.client.HttpClientTransport;
|
||||
import org.eclipse.jetty.client.HttpUpgrader;
|
||||
import org.eclipse.jetty.client.Request;
|
||||
|
@ -40,6 +41,7 @@ import org.eclipse.jetty.client.transport.HttpRequest;
|
|||
import org.eclipse.jetty.client.transport.IConnection;
|
||||
import org.eclipse.jetty.client.transport.SendFailure;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.io.AbstractConnection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
|
@ -61,6 +63,7 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements IConne
|
|||
private final LongAdder bytesIn = new LongAdder();
|
||||
private final LongAdder bytesOut = new LongAdder();
|
||||
private long idleTimeout;
|
||||
private boolean initialize;
|
||||
|
||||
public HttpConnectionOverHTTP(EndPoint endPoint, Map<String, Object> context)
|
||||
{
|
||||
|
@ -113,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()
|
||||
{
|
||||
|
@ -159,12 +168,46 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements IConne
|
|||
return delegate.send(exchange);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether to initialize the connection with an {@code OPTIONS * HTTP/1.1} request.
|
||||
*/
|
||||
public boolean isInitialize()
|
||||
{
|
||||
return initialize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param initialize whether to initialize the connection with an {@code OPTIONS * HTTP/1.1} request.
|
||||
*/
|
||||
public void setInitialize(boolean initialize)
|
||||
{
|
||||
this.initialize = initialize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen()
|
||||
{
|
||||
super.onOpen();
|
||||
fillInterested();
|
||||
promise.succeeded(this);
|
||||
boolean initialize = isInitialize();
|
||||
if (initialize)
|
||||
{
|
||||
Destination destination = getHttpDestination();
|
||||
Request request = destination.getHttpClient().newRequest(destination.getOrigin().asString())
|
||||
.method(HttpMethod.OPTIONS)
|
||||
.path("*");
|
||||
send(request, result ->
|
||||
{
|
||||
if (result.isSucceeded())
|
||||
promise.succeeded(this);
|
||||
else
|
||||
promise.failed(result.getFailure());
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
promise.succeeded(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -313,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)
|
||||
{
|
||||
|
|
|
@ -43,17 +43,18 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpReceiverOverHTTP.class);
|
||||
|
||||
private final Runnable receiveNext = this::receiveNext;
|
||||
private final LongAdder inMessages = new LongAdder();
|
||||
private final HttpParser parser;
|
||||
private final ByteBufferPool byteBufferPool;
|
||||
private RetainableByteBuffer networkBuffer;
|
||||
private boolean shutdown;
|
||||
private boolean complete;
|
||||
private State state = State.STATUS;
|
||||
private boolean unsolicited;
|
||||
private String method;
|
||||
private int status;
|
||||
private String method;
|
||||
private Content.Chunk chunk;
|
||||
private Runnable action;
|
||||
private boolean shutdown;
|
||||
private boolean disposed;
|
||||
|
||||
public HttpReceiverOverHTTP(HttpChannelOverHTTP channel)
|
||||
{
|
||||
|
@ -73,13 +74,15 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
{
|
||||
if (!hasContent())
|
||||
{
|
||||
boolean setFillInterest = parseAndFill();
|
||||
boolean setFillInterest = parseAndFill(true);
|
||||
if (!hasContent() && setFillInterest)
|
||||
fillInterested();
|
||||
}
|
||||
else
|
||||
{
|
||||
responseContentAvailable();
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange != null)
|
||||
responseContentAvailable(exchange);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,10 +98,8 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
super.reset();
|
||||
parser.reset();
|
||||
if (chunk != null)
|
||||
{
|
||||
chunk.release();
|
||||
chunk = null;
|
||||
}
|
||||
chunk = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -107,10 +108,9 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
super.dispose();
|
||||
parser.close();
|
||||
if (chunk != null)
|
||||
{
|
||||
chunk.release();
|
||||
chunk = null;
|
||||
}
|
||||
chunk = null;
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -122,7 +122,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
Content.Chunk chunk = consumeChunk();
|
||||
if (chunk != null)
|
||||
return chunk;
|
||||
boolean needFillInterest = parseAndFill();
|
||||
boolean needFillInterest = parseAndFill(false);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("ParseAndFill needFillInterest {} in {}", needFillInterest, this);
|
||||
chunk = consumeChunk();
|
||||
|
@ -234,7 +234,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
* If this method depletes the buffer, it will always try to re-fill until fill generates 0 byte.
|
||||
* @return true if no bytes were filled.
|
||||
*/
|
||||
private boolean parseAndFill()
|
||||
private boolean parseAndFill(boolean notifyContentAvailable)
|
||||
{
|
||||
HttpConnectionOverHTTP connection = getHttpConnection();
|
||||
EndPoint endPoint = connection.getEndPoint();
|
||||
|
@ -244,23 +244,22 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
acquireNetworkBuffer();
|
||||
while (true)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Parsing {} in {}", networkBuffer, this);
|
||||
// Always parse even empty buffers to advance the parser.
|
||||
if (parse())
|
||||
boolean stopParsing = parse(notifyContentAvailable);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Parsed stop={} in {}", stopParsing, this);
|
||||
if (stopParsing)
|
||||
{
|
||||
// Return immediately, as this thread may be in a race
|
||||
// with e.g. another thread demanding more content.
|
||||
return false;
|
||||
}
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Parser willing to advance in {}", this);
|
||||
|
||||
// Connection may be closed in a parser callback.
|
||||
if (connection.isClosed())
|
||||
if (connection.isClosed() || isShutdown())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Closed {} in {}", connection, this);
|
||||
LOG.debug("Closed/Shutdown {} in {}", connection, this);
|
||||
releaseNetworkBuffer();
|
||||
return false;
|
||||
}
|
||||
|
@ -269,6 +268,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
reacquireNetworkBuffer();
|
||||
|
||||
// The networkBuffer may have been reacquired.
|
||||
assert !networkBuffer.hasRemaining();
|
||||
int read = endPoint.fill(networkBuffer.getByteBuffer());
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Read {} bytes in {} from {} in {}", read, networkBuffer, endPoint, this);
|
||||
|
@ -284,9 +284,9 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
}
|
||||
else
|
||||
{
|
||||
releaseNetworkBuffer();
|
||||
shutdown();
|
||||
return false;
|
||||
// Loop around to parse again to advance the parser,
|
||||
// for example for HTTP/1.0 connection-delimited content.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -305,62 +305,80 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
*
|
||||
* @return true to indicate that parsing should be interrupted (and will be resumed by another thread).
|
||||
*/
|
||||
private boolean parse()
|
||||
private boolean parse(boolean notifyContentAvailable)
|
||||
{
|
||||
// HttpParser is not reentrant, so we cannot invoke the
|
||||
// application from the parser event callbacks.
|
||||
// However, the mechanism in general (and this method)
|
||||
// is reentrant: it notifies the application which may
|
||||
// read response content, which reenters here.
|
||||
|
||||
ByteBuffer byteBuffer = networkBuffer.getByteBuffer();
|
||||
while (true)
|
||||
{
|
||||
boolean handle = parser.parseNext(networkBuffer.getByteBuffer());
|
||||
boolean handle = parser.parseNext(byteBuffer);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Parse result={} on {}", handle, this);
|
||||
Runnable action = getAndSetAction(null);
|
||||
if (action != null)
|
||||
LOG.debug("Parse state={} result={} {} {} on {}", state, handle, BufferUtil.toDetailString(byteBuffer), parser, this);
|
||||
if (!handle)
|
||||
return false;
|
||||
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange == null)
|
||||
throw new IllegalStateException("No exchange");
|
||||
|
||||
switch (state)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Executing action after parser returned: {} on {}", action, this);
|
||||
action.run();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Action executed after Parse result={} on {}", handle, this);
|
||||
}
|
||||
if (handle)
|
||||
{
|
||||
// When the receiver is aborted, the parser is closed in dispose() which changes
|
||||
// its state to State.CLOSE; so checking parser.isClose() is just a way to check
|
||||
// if the receiver was aborted or not.
|
||||
return !parser.isClose();
|
||||
}
|
||||
|
||||
boolean complete = this.complete;
|
||||
this.complete = false;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Parse complete={}, {} {} in {}", complete, networkBuffer, parser, this);
|
||||
|
||||
if (complete)
|
||||
{
|
||||
int status = this.status;
|
||||
this.status = 0;
|
||||
// Connection upgrade due to 101, bail out.
|
||||
if (status == HttpStatus.SWITCHING_PROTOCOLS_101)
|
||||
return true;
|
||||
// Connection upgrade due to CONNECT + 200, bail out.
|
||||
String method = this.method;
|
||||
this.method = null;
|
||||
if (getHttpChannel().isTunnel(method, status))
|
||||
return true;
|
||||
|
||||
if (networkBuffer.isEmpty())
|
||||
return false;
|
||||
|
||||
if (!HttpStatus.isInformational(status))
|
||||
case HEADERS -> responseHeaders(exchange);
|
||||
case CONTENT ->
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Discarding unexpected content after response {}: {} in {}", status, networkBuffer, this);
|
||||
networkBuffer.clear();
|
||||
if (notifyContentAvailable)
|
||||
responseContentAvailable(exchange);
|
||||
}
|
||||
case COMPLETE ->
|
||||
{
|
||||
boolean isUpgrade = status == HttpStatus.SWITCHING_PROTOCOLS_101;
|
||||
boolean isTunnel = getHttpChannel().isTunnel(method, status);
|
||||
|
||||
Runnable task = isUpgrade || isTunnel ? null : this.receiveNext;
|
||||
responseSuccess(exchange, task);
|
||||
|
||||
// Connection upgrade, bail out.
|
||||
if (isUpgrade || isTunnel)
|
||||
return true;
|
||||
|
||||
if (byteBuffer.hasRemaining())
|
||||
{
|
||||
if (HttpStatus.isInterim(status))
|
||||
{
|
||||
// There may be multiple interim responses in
|
||||
// the same network buffer, continue parsing.
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Discarding unexpected content after response {}: {} in {}", status, BufferUtil.toDetailString(byteBuffer), this);
|
||||
BufferUtil.clear(byteBuffer);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Continue to read from the network.
|
||||
return false;
|
||||
}
|
||||
default -> throw new IllegalStateException("Invalid state " + state);
|
||||
}
|
||||
|
||||
// The application may have aborted the request.
|
||||
if (disposed)
|
||||
{
|
||||
BufferUtil.clear(byteBuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (networkBuffer.isEmpty())
|
||||
return false;
|
||||
// The application has been invoked,
|
||||
// and it is now driving the parsing.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -384,7 +402,6 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
// header, the connection will be closed at exchange termination
|
||||
// thanks to the flag we have set above.
|
||||
parser.atEOF();
|
||||
parser.parseNext(BufferUtil.EMPTY_BUFFER);
|
||||
}
|
||||
|
||||
protected boolean isShutdown()
|
||||
|
@ -404,6 +421,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
this.status = status;
|
||||
parser.setHeadResponse(HttpMethod.HEAD.is(method) || getHttpChannel().isTunnel(method, status));
|
||||
exchange.getResponse().version(version).status(status).reason(reason);
|
||||
state = State.STATUS;
|
||||
|
||||
responseBegin(exchange);
|
||||
}
|
||||
|
@ -430,10 +448,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
// Store the EndPoint is case of upgrades, tunnels, etc.
|
||||
exchange.getRequest().getConversation().setAttribute(EndPoint.class.getName(), getHttpConnection().getEndPoint());
|
||||
getHttpConnection().onResponseHeaders(exchange);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Setting action to responseHeaders(exchange, boolean) on {}", this);
|
||||
if (getAndSetAction(() -> responseHeaders(exchange)) != null)
|
||||
throw new IllegalStateException();
|
||||
state = State.HEADERS;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -449,17 +464,13 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
|
||||
if (chunk != null)
|
||||
throw new IllegalStateException("Content generated with unconsumed content left");
|
||||
if (getHttpConnection().isFillInterested())
|
||||
throw new IllegalStateException("Fill interested while parsing for content");
|
||||
|
||||
// Retain the chunk because it is stored for later use.
|
||||
networkBuffer.retain();
|
||||
chunk = Content.Chunk.asChunk(buffer, false, networkBuffer);
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Setting action to responseContentAvailable on {}", this);
|
||||
if (getAndSetAction(this::responseContentAvailable) != null)
|
||||
throw new IllegalStateException();
|
||||
if (getHttpConnection().isFillInterested())
|
||||
throw new IllegalStateException();
|
||||
state = State.CONTENT;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -489,28 +500,20 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
if (exchange == null || unsolicited)
|
||||
{
|
||||
// We received an unsolicited response from the server.
|
||||
networkBuffer.clear();
|
||||
getHttpConnection().close();
|
||||
return false;
|
||||
}
|
||||
|
||||
int status = exchange.getResponse().getStatus();
|
||||
if (!HttpStatus.isInterim(status))
|
||||
{
|
||||
inMessages.increment();
|
||||
complete = true;
|
||||
}
|
||||
|
||||
if (chunk != null)
|
||||
throw new IllegalStateException();
|
||||
chunk = Content.Chunk.EOF;
|
||||
|
||||
boolean isUpgrade = status == HttpStatus.SWITCHING_PROTOCOLS_101;
|
||||
boolean isTunnel = getHttpChannel().isTunnel(method, status);
|
||||
Runnable task = isUpgrade || isTunnel ? null : this::receiveNext;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Message complete, calling response success with task {} in {}", task, this);
|
||||
responseSuccess(exchange, task);
|
||||
return false;
|
||||
state = State.COMPLETE;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void receiveNext()
|
||||
|
@ -522,7 +525,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Receiving next request in {}", this);
|
||||
boolean setFillInterest = parseAndFill();
|
||||
boolean setFillInterest = parseAndFill(true);
|
||||
if (!hasContent() && setFillInterest)
|
||||
fillInterested();
|
||||
}
|
||||
|
@ -554,13 +557,6 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
}
|
||||
}
|
||||
|
||||
private Runnable getAndSetAction(Runnable action)
|
||||
{
|
||||
Runnable r = this.action;
|
||||
this.action = action;
|
||||
return r;
|
||||
}
|
||||
|
||||
long getMessagesIn()
|
||||
{
|
||||
return inMessages.longValue();
|
||||
|
@ -571,4 +567,9 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
{
|
||||
return String.format("%s[%s]", super.toString(), parser);
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
STATUS, HEADERS, CONTENT, COMPLETE
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -703,7 +703,7 @@ public class ConnectionPoolTest
|
|||
assertThat(connectionPool.toString(), not(nullValue()));
|
||||
}
|
||||
|
||||
private static class ConnectionPoolFactory
|
||||
public static class ConnectionPoolFactory
|
||||
{
|
||||
private final String name;
|
||||
private final ConnectionPool.Factory factory;
|
||||
|
|
|
@ -1865,7 +1865,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
});
|
||||
// Close the parser to cause the issue.
|
||||
org.eclipse.jetty.server.HttpConnection.getCurrentConnection().getParser().close();
|
||||
org.eclipse.jetty.server.internal.HttpConnection.getCurrentConnection().getParser().close();
|
||||
}
|
||||
});
|
||||
server.start();
|
||||
|
|
|
@ -332,7 +332,10 @@ public class NetworkTrafficListenerTest
|
|||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback)
|
||||
{
|
||||
Response.sendRedirect(request, response, callback, location);
|
||||
Content.Source.consumeAll(request, Callback.from(
|
||||
() -> Response.sendRedirect(request, response, callback, location),
|
||||
callback::failed
|
||||
));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -31,12 +31,11 @@ public class HttpClientJMXTest
|
|||
@Test
|
||||
public void testHttpClientName() throws Exception
|
||||
{
|
||||
String name = "foo";
|
||||
HttpClient httpClient = new HttpClient();
|
||||
httpClient.setName(name);
|
||||
|
||||
try
|
||||
try (HttpClient httpClient = new HttpClient())
|
||||
{
|
||||
String name = "foo";
|
||||
httpClient.setName(name);
|
||||
|
||||
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
|
||||
MBeanContainer mbeanContainer = new MBeanContainer(mbeanServer);
|
||||
// Adding MBeanContainer as a bean will trigger the registration of MBeans.
|
||||
|
@ -59,9 +58,5 @@ public class HttpClientJMXTest
|
|||
assertEquals(name, oName.getKeyProperty("context"));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
httpClient.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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[]
|
||||
|
|
|
@ -119,11 +119,25 @@ public class HttpChannelOverFCGI extends HttpChannel
|
|||
receiver.content(chunk);
|
||||
}
|
||||
|
||||
protected void responseContentAvailable()
|
||||
{
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange != null)
|
||||
receiver.responseContentAvailable(exchange);
|
||||
}
|
||||
|
||||
protected void end()
|
||||
{
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange != null)
|
||||
receiver.end(exchange);
|
||||
receiver.end();
|
||||
}
|
||||
|
||||
protected void responseSuccess()
|
||||
{
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange != null)
|
||||
receiver.responseSuccess(exchange);
|
||||
}
|
||||
|
||||
protected void responseFailure(Throwable failure, Promise<Boolean> promise)
|
||||
|
|
|
@ -67,7 +67,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
private final HttpChannelOverFCGI channel;
|
||||
private RetainableByteBuffer networkBuffer;
|
||||
private Object attachment;
|
||||
private Runnable action;
|
||||
private State state = State.STATUS;
|
||||
private long idleTimeout;
|
||||
private boolean shutdown;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -162,7 +168,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
this.networkBuffer = null;
|
||||
}
|
||||
|
||||
boolean parseAndFill()
|
||||
boolean parseAndFill(boolean notifyContentAvailable)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("parseAndFill {}", networkBuffer);
|
||||
|
@ -173,7 +179,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
{
|
||||
while (true)
|
||||
{
|
||||
if (parse(networkBuffer.getByteBuffer()))
|
||||
if (parse(networkBuffer.getByteBuffer(), notifyContentAvailable))
|
||||
return false;
|
||||
|
||||
if (networkBuffer.isRetained())
|
||||
|
@ -208,13 +214,35 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
}
|
||||
}
|
||||
|
||||
private boolean parse(ByteBuffer buffer)
|
||||
private boolean parse(ByteBuffer buffer, boolean notifyContentAvailable)
|
||||
{
|
||||
boolean parse = parser.parse(buffer);
|
||||
Runnable action = getAndSetAction(null);
|
||||
if (action != null)
|
||||
action.run();
|
||||
return parse;
|
||||
boolean handle = parser.parse(buffer);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case STATUS ->
|
||||
{
|
||||
// Nothing to do.
|
||||
}
|
||||
case HEADERS -> channel.responseHeaders();
|
||||
case CONTENT ->
|
||||
{
|
||||
if (notifyContentAvailable)
|
||||
channel.responseContentAvailable();
|
||||
}
|
||||
case COMPLETE ->
|
||||
{
|
||||
// For the complete event, handle==false, and cannot
|
||||
// differentiate between a complete event and a parse()
|
||||
// with zero or not enough bytes, so the state is reset
|
||||
// here to avoid calling responseSuccess() again.
|
||||
state = State.STATUS;
|
||||
channel.responseSuccess();
|
||||
}
|
||||
default -> throw new IllegalStateException("Invalid state " + state);
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
private void shutdown()
|
||||
|
@ -312,13 +340,6 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
}, x -> close(failure)));
|
||||
}
|
||||
|
||||
private Runnable getAndSetAction(Runnable action)
|
||||
{
|
||||
Runnable r = this.action;
|
||||
this.action = action;
|
||||
return r;
|
||||
}
|
||||
|
||||
protected HttpChannelOverFCGI newHttpChannel()
|
||||
{
|
||||
return new HttpChannelOverFCGI(this);
|
||||
|
@ -359,6 +380,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)
|
||||
{
|
||||
|
@ -402,6 +429,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onBegin r={},c={},reason={}", request, code, reason);
|
||||
state = State.STATUS;
|
||||
channel.responseBegin(code, reason);
|
||||
}
|
||||
|
||||
|
@ -418,8 +446,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onHeaders r={} {}", request, networkBuffer);
|
||||
if (getAndSetAction(channel::responseHeaders) != null)
|
||||
throw new IllegalStateException();
|
||||
state = State.HEADERS;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -432,13 +459,10 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
{
|
||||
case STD_OUT ->
|
||||
{
|
||||
// No need to call networkBuffer.retain() here, since we know
|
||||
// that the action will be run before releasing the networkBuffer.
|
||||
// The receiver of the chunk decides whether to consume/retain it.
|
||||
Content.Chunk chunk = Content.Chunk.asChunk(buffer, false, networkBuffer);
|
||||
if (getAndSetAction(() -> channel.content(chunk)) == null)
|
||||
return true;
|
||||
throw new IllegalStateException();
|
||||
channel.content(chunk);
|
||||
state = State.CONTENT;
|
||||
return true;
|
||||
}
|
||||
case STD_ERR -> LOG.info(BufferUtil.toUTF8String(buffer));
|
||||
default -> throw new IllegalArgumentException();
|
||||
|
@ -452,6 +476,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onEnd r={}", request);
|
||||
channel.end();
|
||||
state = State.COMPLETE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -462,4 +487,9 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
|
|||
failAndClose(failure);
|
||||
}
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
STATUS, HEADERS, CONTENT, COMPLETE
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,13 +34,15 @@ public class HttpReceiverOverFCGI extends HttpReceiver
|
|||
if (!hasContent())
|
||||
{
|
||||
HttpConnectionOverFCGI httpConnection = getHttpChannel().getHttpConnection();
|
||||
boolean setFillInterest = httpConnection.parseAndFill();
|
||||
boolean setFillInterest = httpConnection.parseAndFill(true);
|
||||
if (!hasContent() && setFillInterest)
|
||||
httpConnection.fillInterested();
|
||||
}
|
||||
else
|
||||
{
|
||||
responseContentAvailable();
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange != null)
|
||||
responseContentAvailable(exchange);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +81,7 @@ public class HttpReceiverOverFCGI extends HttpReceiver
|
|||
if (chunk != null)
|
||||
return chunk;
|
||||
HttpConnectionOverFCGI httpConnection = getHttpChannel().getHttpConnection();
|
||||
boolean needFillInterest = httpConnection.parseAndFill();
|
||||
boolean needFillInterest = httpConnection.parseAndFill(false);
|
||||
chunk = consumeChunk();
|
||||
if (chunk != null)
|
||||
return chunk;
|
||||
|
@ -112,15 +114,18 @@ public class HttpReceiverOverFCGI extends HttpReceiver
|
|||
// Retain the chunk because it is stored for later reads.
|
||||
chunk.retain();
|
||||
this.chunk = chunk;
|
||||
responseContentAvailable();
|
||||
}
|
||||
|
||||
void end(HttpExchange exchange)
|
||||
void end()
|
||||
{
|
||||
if (chunk != null)
|
||||
throw new IllegalStateException();
|
||||
chunk = Content.Chunk.EOF;
|
||||
responseSuccess(exchange, this::receiveNext);
|
||||
}
|
||||
|
||||
void responseSuccess(HttpExchange exchange)
|
||||
{
|
||||
super.responseSuccess(exchange, this::receiveNext);
|
||||
}
|
||||
|
||||
private void receiveNext()
|
||||
|
@ -131,7 +136,7 @@ public class HttpReceiverOverFCGI extends HttpReceiver
|
|||
throw new IllegalStateException();
|
||||
|
||||
HttpConnectionOverFCGI httpConnection = getHttpChannel().getHttpConnection();
|
||||
boolean setFillInterest = httpConnection.parseAndFill();
|
||||
boolean setFillInterest = httpConnection.parseAndFill(true);
|
||||
if (!hasContent() && setFillInterest)
|
||||
httpConnection.fillInterested();
|
||||
}
|
||||
|
@ -160,6 +165,12 @@ public class HttpReceiverOverFCGI extends HttpReceiver
|
|||
super.responseHeaders(exchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void responseContentAvailable(HttpExchange exchange)
|
||||
{
|
||||
super.responseContentAvailable(exchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void responseFailure(Throwable failure, Promise<Boolean> promise)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,6 @@ package org.eclipse.jetty.fcgi.parser;
|
|||
|
||||
import java.io.EOFException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jetty.fcgi.FCGI;
|
||||
import org.eclipse.jetty.http.HttpCompliance;
|
||||
|
@ -43,13 +41,12 @@ public class ResponseContentParser extends StreamContentParser
|
|||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResponseContentParser.class);
|
||||
|
||||
private final Map<Integer, ResponseParser> parsers = new ConcurrentHashMap<>();
|
||||
private final ClientParser.Listener listener;
|
||||
private final ResponseParser parser;
|
||||
|
||||
public ResponseContentParser(HeaderParser headerParser, ClientParser.Listener listener)
|
||||
{
|
||||
super(headerParser, FCGI.StreamType.STD_OUT, listener);
|
||||
this.listener = listener;
|
||||
this.parser = new ResponseParser(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -63,13 +60,6 @@ public class ResponseContentParser extends StreamContentParser
|
|||
@Override
|
||||
protected boolean onContent(ByteBuffer buffer)
|
||||
{
|
||||
int request = getRequest();
|
||||
ResponseParser parser = parsers.get(request);
|
||||
if (parser == null)
|
||||
{
|
||||
parser = new ResponseParser(listener, request);
|
||||
parsers.put(request, parser);
|
||||
}
|
||||
return parser.parse(buffer);
|
||||
}
|
||||
|
||||
|
@ -77,37 +67,44 @@ public class ResponseContentParser extends StreamContentParser
|
|||
protected void end(int request)
|
||||
{
|
||||
super.end(request);
|
||||
parsers.remove(request);
|
||||
parser.reset();
|
||||
}
|
||||
|
||||
private static class ResponseParser implements HttpParser.ResponseHandler
|
||||
private class ResponseParser implements HttpParser.ResponseHandler
|
||||
{
|
||||
private final HttpFields.Mutable fields = HttpFields.build();
|
||||
private final ClientParser.Listener listener;
|
||||
private final int request;
|
||||
private final FCGIHttpParser httpParser;
|
||||
private State state = State.HEADERS;
|
||||
private boolean seenResponseCode;
|
||||
private boolean stalled;
|
||||
|
||||
private ResponseParser(ClientParser.Listener listener, int request)
|
||||
private ResponseParser(ClientParser.Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
this.request = request;
|
||||
this.httpParser = new FCGIHttpParser(this);
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
fields.clear();
|
||||
httpParser.reset();
|
||||
state = State.HEADERS;
|
||||
seenResponseCode = false;
|
||||
stalled = false;
|
||||
}
|
||||
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
int remaining = buffer.remaining();
|
||||
while (remaining > 0)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Response {} {}, state {} {}", request, FCGI.StreamType.STD_OUT, state, BufferUtil.toDetailString(buffer));
|
||||
LOG.debug("Response {} {}, state {} {}", getRequest(), FCGI.StreamType.STD_OUT, state, BufferUtil.toDetailString(buffer));
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case HEADERS:
|
||||
case HEADERS ->
|
||||
{
|
||||
if (httpParser.parseNext(buffer))
|
||||
{
|
||||
|
@ -116,40 +113,33 @@ public class ResponseContentParser extends StreamContentParser
|
|||
return true;
|
||||
}
|
||||
remaining = buffer.remaining();
|
||||
break;
|
||||
}
|
||||
case CONTENT_MODE:
|
||||
case CONTENT_MODE ->
|
||||
{
|
||||
// If we have no indication of the content, then
|
||||
// the HTTP parser will assume there is no content
|
||||
// and will not parse it even if it is provided,
|
||||
// so we have to parse it raw ourselves here.
|
||||
boolean rawContent = fields.size() == 0 ||
|
||||
(fields.get(HttpHeader.CONTENT_LENGTH) == null &&
|
||||
fields.get(HttpHeader.TRANSFER_ENCODING) == null);
|
||||
(fields.get(HttpHeader.CONTENT_LENGTH) == null &&
|
||||
fields.get(HttpHeader.TRANSFER_ENCODING) == null);
|
||||
state = rawContent ? State.RAW_CONTENT : State.HTTP_CONTENT;
|
||||
break;
|
||||
}
|
||||
case RAW_CONTENT:
|
||||
case RAW_CONTENT ->
|
||||
{
|
||||
ByteBuffer content = buffer.asReadOnlyBuffer();
|
||||
buffer.position(buffer.limit());
|
||||
if (notifyContent(content))
|
||||
return true;
|
||||
remaining = 0;
|
||||
break;
|
||||
}
|
||||
case HTTP_CONTENT:
|
||||
case HTTP_CONTENT ->
|
||||
{
|
||||
if (httpParser.parseNext(buffer))
|
||||
return true;
|
||||
remaining = buffer.remaining();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
default -> throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -205,7 +195,7 @@ public class ResponseContentParser extends StreamContentParser
|
|||
{
|
||||
try
|
||||
{
|
||||
listener.onBegin(request, code, reason);
|
||||
listener.onBegin(getRequest(), code, reason);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
|
@ -218,7 +208,7 @@ public class ResponseContentParser extends StreamContentParser
|
|||
{
|
||||
try
|
||||
{
|
||||
listener.onHeader(request, httpField);
|
||||
listener.onHeader(getRequest(), httpField);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
|
@ -242,7 +232,7 @@ public class ResponseContentParser extends StreamContentParser
|
|||
{
|
||||
try
|
||||
{
|
||||
return listener.onHeaders(request);
|
||||
return listener.onHeaders(getRequest());
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
|
@ -278,7 +268,7 @@ public class ResponseContentParser extends StreamContentParser
|
|||
{
|
||||
try
|
||||
{
|
||||
return listener.onContent(request, FCGI.StreamType.STD_OUT, buffer);
|
||||
return listener.onContent(getRequest(), FCGI.StreamType.STD_OUT, buffer);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
|
@ -318,7 +308,7 @@ public class ResponseContentParser extends StreamContentParser
|
|||
{
|
||||
try
|
||||
{
|
||||
listener.onFailure(request, failure);
|
||||
listener.onFailure(getRequest(), failure);
|
||||
}
|
||||
catch (Throwable 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()
|
||||
|
|
|
@ -15,7 +15,6 @@ package org.eclipse.jetty.fcgi.server;
|
|||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.awaitility.Awaitility;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpClientTransport;
|
||||
import org.eclipse.jetty.fcgi.client.transport.HttpClientTransportOverFCGI;
|
||||
|
@ -29,10 +28,11 @@ import org.eclipse.jetty.server.ServerConnector;
|
|||
import org.eclipse.jetty.util.ProcessorUtils;
|
||||
import org.eclipse.jetty.util.component.LifeCycle;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public abstract class AbstractHttpClientServerTest
|
||||
{
|
||||
|
@ -75,9 +75,9 @@ public abstract class AbstractHttpClientServerTest
|
|||
try
|
||||
{
|
||||
if (serverBufferPool != null)
|
||||
assertNoLeaks(serverBufferPool, "\n---\nServer Leaks: " + serverBufferPool.dumpLeaks() + "---\n");
|
||||
await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat("Server Leaks: " + serverBufferPool.dumpLeaks(), serverBufferPool.getLeaks().size(), is(0)));
|
||||
if (clientBufferPool != null)
|
||||
assertNoLeaks(clientBufferPool, "\n---\nClient Leaks: " + clientBufferPool.dumpLeaks() + "---\n");
|
||||
await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat("Client Leaks: " + clientBufferPool.dumpLeaks(), clientBufferPool.getLeaks().size(), is(0)));
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -85,16 +85,4 @@ public abstract class AbstractHttpClientServerTest
|
|||
LifeCycle.stop(server);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertNoLeaks(ArrayByteBufferPool.Tracking bufferPool, String msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
Awaitility.await().atMost(3, TimeUnit.SECONDS).until(() -> bufferPool.getLeaks().size(), Matchers.is(0));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
fail(e.getMessage() + msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -699,7 +699,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
|
|||
* @return the value of the field as a {@code long},
|
||||
* or -1 if no such field is present
|
||||
* @throws NumberFormatException if the value of the field
|
||||
* cannot be converted to a {@link long}
|
||||
* cannot be converted to a {@code long}
|
||||
*/
|
||||
default long getLongField(String name) throws NumberFormatException
|
||||
{
|
||||
|
@ -715,7 +715,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
|
|||
* @return the value of the field as a {@code long},
|
||||
* or -1 if no such field is present
|
||||
* @throws NumberFormatException if the value of the field
|
||||
* cannot be converted to a {@link long}
|
||||
* cannot be converted to a {@code long}
|
||||
*/
|
||||
default long getLongField(HttpHeader header) throws NumberFormatException
|
||||
{
|
||||
|
|
|
@ -455,12 +455,18 @@ public class HttpGenerator
|
|||
}
|
||||
}
|
||||
|
||||
public void servletUpgrade()
|
||||
public void startTunnel()
|
||||
{
|
||||
_noContentResponse = false;
|
||||
_state = State.COMMITTED;
|
||||
}
|
||||
|
||||
@Deprecated(since = "12.1.0", forRemoval = true)
|
||||
public void servletUpgrade()
|
||||
{
|
||||
startTunnel();
|
||||
}
|
||||
|
||||
private void prepareChunk(ByteBuffer chunk, int remaining)
|
||||
{
|
||||
// if we need CRLF add this to header
|
||||
|
|
|
@ -2016,11 +2016,17 @@ public class HttpParser
|
|||
_headerComplete = false;
|
||||
}
|
||||
|
||||
public void startTunnel()
|
||||
{
|
||||
setState(State.EOF_CONTENT);
|
||||
_endOfContent = EndOfContent.EOF_CONTENT;
|
||||
_contentLength = -1;
|
||||
}
|
||||
|
||||
@Deprecated(since = "12.1.0", forRemoval = true)
|
||||
public void servletUpgrade()
|
||||
{
|
||||
setState(State.CONTENT);
|
||||
_endOfContent = EndOfContent.UNKNOWN_CONTENT;
|
||||
_contentLength = -1;
|
||||
startTunnel();
|
||||
}
|
||||
|
||||
protected void setState(State state)
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.nio.charset.Charset;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
|
||||
/**
|
||||
|
@ -62,7 +63,7 @@ public class HttpTester
|
|||
|
||||
public Input()
|
||||
{
|
||||
this(BufferUtil.allocate(8192));
|
||||
this(BufferUtil.allocate(IO.DEFAULT_BUFFER_SIZE));
|
||||
}
|
||||
|
||||
Input(ByteBuffer buffer)
|
||||
|
@ -477,7 +478,7 @@ public class HttpTester
|
|||
switch (result)
|
||||
{
|
||||
case NEED_HEADER:
|
||||
header = BufferUtil.allocate(8192);
|
||||
header = BufferUtil.allocate(IO.DEFAULT_BUFFER_SIZE);
|
||||
continue;
|
||||
|
||||
case HEADER_OVERFLOW:
|
||||
|
@ -491,7 +492,7 @@ public class HttpTester
|
|||
continue;
|
||||
|
||||
case NEED_CHUNK_TRAILER:
|
||||
chunk = BufferUtil.allocate(8192);
|
||||
chunk = BufferUtil.allocate(IO.DEFAULT_BUFFER_SIZE);
|
||||
continue;
|
||||
|
||||
case NEED_INFO:
|
||||
|
|
|
@ -564,7 +564,7 @@ public class MultiPart
|
|||
public abstract static class AbstractContentSource implements Content.Source, Closeable
|
||||
{
|
||||
private final AutoLock lock = new AutoLock();
|
||||
private final SerializedInvoker invoker = new SerializedInvoker();
|
||||
private final SerializedInvoker invoker = new SerializedInvoker(AbstractContentSource.class);
|
||||
private final Queue<Part> parts = new ArrayDeque<>();
|
||||
private final String boundary;
|
||||
private final ByteBuffer firstBoundary;
|
||||
|
|
|
@ -222,7 +222,7 @@ public class MultiPartByteRanges
|
|||
{
|
||||
private final Resource resource;
|
||||
private final ByteRange byteRange;
|
||||
private final ByteBufferPool bufferPool;
|
||||
private final ByteBufferPool.Sized bufferPool;
|
||||
|
||||
public Part(String contentType, Resource resource, ByteRange byteRange, long contentLength)
|
||||
{
|
||||
|
@ -233,7 +233,7 @@ public class MultiPartByteRanges
|
|||
public Part(String contentType, Resource resource, ByteRange byteRange, long contentLength, ByteBufferPool bufferPool)
|
||||
{
|
||||
this(HttpFields.build().put(HttpHeader.CONTENT_TYPE, contentType)
|
||||
.put(HttpHeader.CONTENT_RANGE, byteRange.toHeaderValue(contentLength)), resource, byteRange, bufferPool);
|
||||
.put(HttpHeader.CONTENT_RANGE, byteRange.toHeaderValue(contentLength)), resource, byteRange, new ByteBufferPool.Sized(bufferPool));
|
||||
}
|
||||
|
||||
public Part(HttpFields headers, Resource resource, ByteRange byteRange)
|
||||
|
@ -241,18 +241,18 @@ public class MultiPartByteRanges
|
|||
this(headers, resource, byteRange, null);
|
||||
}
|
||||
|
||||
public Part(HttpFields headers, Resource resource, ByteRange byteRange, ByteBufferPool bufferPool)
|
||||
public Part(HttpFields headers, Resource resource, ByteRange byteRange, ByteBufferPool.Sized bufferPool)
|
||||
{
|
||||
super(null, null, headers);
|
||||
this.resource = resource;
|
||||
this.byteRange = byteRange;
|
||||
this.bufferPool = bufferPool == null ? ByteBufferPool.NON_POOLING : bufferPool;
|
||||
this.bufferPool = bufferPool == null ? ByteBufferPool.SIZED_NON_POOLING : bufferPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Content.Source newContentSource()
|
||||
{
|
||||
return IOResources.asContentSource(resource, bufferPool, 0, false, byteRange.first(), byteRange.getLength());
|
||||
return IOResources.asContentSource(resource, bufferPool, byteRange.first(), byteRange.getLength());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -69,16 +69,15 @@ public class CachingHttpContentFactory implements HttpContent.Factory
|
|||
private final HttpContent.Factory _authority;
|
||||
private final ConcurrentHashMap<String, CachingHttpContent> _cache = new ConcurrentHashMap<>();
|
||||
private final AtomicLong _cachedSize = new AtomicLong();
|
||||
private final ByteBufferPool _bufferPool;
|
||||
private final ByteBufferPool.Sized _bufferPool;
|
||||
private int _maxCachedFileSize = DEFAULT_MAX_CACHED_FILE_SIZE;
|
||||
private int _maxCachedFiles = DEFAULT_MAX_CACHED_FILES;
|
||||
private long _maxCacheSize = DEFAULT_MAX_CACHE_SIZE;
|
||||
private boolean _useDirectByteBuffers = true;
|
||||
|
||||
public CachingHttpContentFactory(HttpContent.Factory authority, ByteBufferPool bufferPool)
|
||||
public CachingHttpContentFactory(HttpContent.Factory authority, ByteBufferPool.Sized bufferPool)
|
||||
{
|
||||
_authority = authority;
|
||||
_bufferPool = bufferPool != null ? bufferPool : ByteBufferPool.NON_POOLING;
|
||||
_bufferPool = bufferPool != null ? bufferPool : ByteBufferPool.SIZED_NON_POOLING;
|
||||
}
|
||||
|
||||
protected ConcurrentMap<String, CachingHttpContent> getCache()
|
||||
|
@ -137,16 +136,6 @@ public class CachingHttpContentFactory implements HttpContent.Factory
|
|||
shrinkCache();
|
||||
}
|
||||
|
||||
public boolean isUseDirectByteBuffers()
|
||||
{
|
||||
return _useDirectByteBuffers;
|
||||
}
|
||||
|
||||
public void setUseDirectByteBuffers(boolean useDirectByteBuffers)
|
||||
{
|
||||
_useDirectByteBuffers = useDirectByteBuffers;
|
||||
}
|
||||
|
||||
private void shrinkCache()
|
||||
{
|
||||
// While we need to shrink
|
||||
|
@ -334,7 +323,7 @@ public class CachingHttpContentFactory implements HttpContent.Factory
|
|||
throw new IllegalArgumentException("Resource is too large: length " + contentLengthValue + " > " + _maxCachedFileSize);
|
||||
|
||||
// Read the content into memory
|
||||
_buffer = IOResources.toRetainableByteBuffer(httpContent.getResource(), _bufferPool, _useDirectByteBuffers);
|
||||
_buffer = IOResources.toRetainableByteBuffer(httpContent.getResource(), _bufferPool);
|
||||
|
||||
_characterEncoding = httpContent.getCharacterEncoding();
|
||||
_compressedFormats = httpContent.getPreCompressedContentFormats();
|
||||
|
|
|
@ -121,7 +121,7 @@ public class ResourceHttpContent implements HttpContent
|
|||
@Override
|
||||
public void writeTo(Content.Sink sink, long offset, long length, Callback callback)
|
||||
{
|
||||
IOResources.copy(_resource, sink, _sizedBufferPool, _sizedBufferPool.getSize(), _sizedBufferPool.isDirect(), offset, length, callback);
|
||||
IOResources.copy(_resource, sink, _sizedBufferPool, offset, length, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -59,7 +59,7 @@ public class ValidatingCachingHttpContentFactory extends CachingHttpContentFacto
|
|||
*/
|
||||
public ValidatingCachingHttpContentFactory(@Name("authority") HttpContent.Factory authority,
|
||||
@Name("validationPeriod") long validationPeriod,
|
||||
@Name("bufferPool") ByteBufferPool bufferPool)
|
||||
@Name("bufferPool") ByteBufferPool.Sized bufferPool)
|
||||
{
|
||||
this(authority, validationPeriod, bufferPool, null, -1, -1);
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ public class ValidatingCachingHttpContentFactory extends CachingHttpContentFacto
|
|||
*/
|
||||
public ValidatingCachingHttpContentFactory(@Name("authority") HttpContent.Factory authority,
|
||||
@Name("validationPeriod") long validationPeriod,
|
||||
@Name("byteBufferPool") ByteBufferPool bufferPool,
|
||||
@Name("byteBufferPool") ByteBufferPool.Sized bufferPool,
|
||||
@Name("scheduler") Scheduler scheduler,
|
||||
@Name("sweepPeriod") long sweepPeriod,
|
||||
@Name("idleTimeout") long idleTimeout)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -50,8 +50,6 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
|
|||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpReceiverOverHTTP2.class);
|
||||
|
||||
private final Runnable onDataAvailableTask = new Invocable.ReadyTask(Invocable.InvocationType.NON_BLOCKING, this::responseContentAvailable);
|
||||
|
||||
public HttpReceiverOverHTTP2(HttpChannel channel)
|
||||
{
|
||||
super(channel);
|
||||
|
@ -70,6 +68,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
|
|||
Stream stream = getHttpChannel().getStream();
|
||||
if (stream == null)
|
||||
return Content.Chunk.from(new EOFException("Channel has been released"));
|
||||
|
||||
Stream.Data data = stream.readData();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Read stream data {} in {}", data, this);
|
||||
|
@ -79,13 +78,25 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
|
|||
stream.demand();
|
||||
return null;
|
||||
}
|
||||
|
||||
DataFrame frame = data.frame();
|
||||
boolean last = frame.remaining() == 0 && frame.isEndStream();
|
||||
if (!last)
|
||||
return Content.Chunk.asChunk(frame.getByteBuffer(), last, data);
|
||||
return Content.Chunk.asChunk(frame.getByteBuffer(), false, data);
|
||||
|
||||
data.release();
|
||||
responseSuccess(getHttpExchange(), null);
|
||||
return Content.Chunk.EOF;
|
||||
|
||||
if (stream.isReset())
|
||||
{
|
||||
Throwable failure = new EOFException("Stream has been reset");
|
||||
responseFailure(failure, Promise.noop());
|
||||
return Content.Chunk.from(failure);
|
||||
}
|
||||
else
|
||||
{
|
||||
responseSuccess(getHttpExchange(), null);
|
||||
return Content.Chunk.EOF;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -213,7 +224,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
|
|||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange == null)
|
||||
return null;
|
||||
return onDataAvailableTask;
|
||||
return new Invocable.ReadyTask(Invocable.InvocationType.NON_BLOCKING, () -> responseContentAvailable(exchange));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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
|
|
@ -34,6 +34,7 @@ import org.eclipse.jetty.io.ClientConnectionFactory;
|
|||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.io.Transport;
|
||||
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||
|
@ -99,10 +100,10 @@ import org.eclipse.jetty.util.thread.Scheduler;
|
|||
*} </pre>
|
||||
*/
|
||||
@ManagedObject
|
||||
public class HTTP2Client extends ContainerLifeCycle
|
||||
public class HTTP2Client extends ContainerLifeCycle implements AutoCloseable
|
||||
{
|
||||
private final ClientConnector connector;
|
||||
private int inputBufferSize = 8192;
|
||||
private int inputBufferSize = IO.DEFAULT_BUFFER_SIZE;
|
||||
private List<String> protocols = List.of("h2");
|
||||
private int initialSessionRecvWindow = 16 * 1024 * 1024;
|
||||
private int initialStreamRecvWindow = 8 * 1024 * 1024;
|
||||
|
@ -492,4 +493,10 @@ public class HTTP2Client extends ContainerLifeCycle
|
|||
}
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception
|
||||
{
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -51,7 +54,6 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
|
|||
private final Collection<HTTP2Session.Entry> processedEntries = new ArrayList<>();
|
||||
private final HTTP2Session session;
|
||||
private final RetainableByteBuffer.Mutable accumulator;
|
||||
private boolean released;
|
||||
private InvocationType invocationType = InvocationType.NON_BLOCKING;
|
||||
private Throwable terminated;
|
||||
private HTTP2Session.Entry stalledEntry;
|
||||
|
@ -95,10 +97,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 +110,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 +136,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 +168,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 +267,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.
|
||||
|
@ -308,7 +333,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
|
|||
|
||||
private void finish()
|
||||
{
|
||||
release();
|
||||
accumulator.clear();
|
||||
processedEntries.forEach(HTTP2Session.Entry::succeeded);
|
||||
processedEntries.clear();
|
||||
invocationType = InvocationType.NON_BLOCKING;
|
||||
|
@ -328,26 +353,38 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
|
|||
}
|
||||
}
|
||||
|
||||
private void release()
|
||||
{
|
||||
if (!released)
|
||||
{
|
||||
released = true;
|
||||
accumulator.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCompleteSuccess()
|
||||
{
|
||||
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();
|
||||
accumulator.release();
|
||||
}
|
||||
|
||||
private void onSessionFailure(Throwable x)
|
||||
{
|
||||
accumulator.clear();
|
||||
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 +406,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">
|
||||
|
|
|
@ -72,34 +72,40 @@
|
|||
--add-reads org.eclipse.jetty.http2.server=org.eclipse.jetty.logging</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.mortbay.jetty</groupId>
|
||||
<artifactId>h2spec-maven-plugin</artifactId>
|
||||
<version>1.0.12</version>
|
||||
<configuration>
|
||||
<mainClass>org.eclipse.jetty.http2.tests.H2SpecServer</mainClass>
|
||||
<skip>${h2spec.skip}</skip>
|
||||
<junitPackage>org.eclipse.jetty.h2spec</junitPackage>
|
||||
<skipNoDockerAvailable>true</skipNoDockerAvailable>
|
||||
<reportsDirectory>${project.build.directory}/h2spec-reports</reportsDirectory>
|
||||
<excludeSpecs>
|
||||
<excludeSpec>3.5 - Sends invalid connection preface</excludeSpec>
|
||||
</excludeSpecs>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>h2spec</id>
|
||||
<goals>
|
||||
<goal>h2spec</goal>
|
||||
</goals>
|
||||
<phase>test</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>h2spec</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.mortbay.jetty</groupId>
|
||||
<artifactId>h2spec-maven-plugin</artifactId>
|
||||
<version>1.0.13</version>
|
||||
<configuration>
|
||||
<mainClass>org.eclipse.jetty.http2.tests.H2SpecServer</mainClass>
|
||||
<skip>${h2spec.skip}</skip>
|
||||
<junitPackage>org.eclipse.jetty.h2spec</junitPackage>
|
||||
<skipNoDockerAvailable>true</skipNoDockerAvailable>
|
||||
<excludeSpecs>
|
||||
<excludeSpec>3.5 - Sends invalid connection preface</excludeSpec>
|
||||
</excludeSpecs>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>h2spec</id>
|
||||
<goals>
|
||||
<goal>h2spec</goal>
|
||||
</goals>
|
||||
<phase>test</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>run-spec-server</id>
|
||||
<build>
|
||||
|
|
|
@ -122,7 +122,7 @@ public class BadURITest
|
|||
Thread.sleep(1000);
|
||||
|
||||
// Send a second request and verify that it hits the Handler.
|
||||
accumulator.release();
|
||||
accumulator.clear();
|
||||
MetaData.Request metaData2 = new MetaData.Request(
|
||||
HttpMethod.GET.asString(),
|
||||
HttpScheme.HTTP.asString(),
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue