From 6597727c863cbfd884d3099fc14994b5bc979c77 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Thu, 22 Jun 2023 11:53:50 +0200 Subject: [PATCH] Upgrade to Jetty 12 This commit upgrades Spring Framework to Jetty 12.0.1, and Reactive HTTP Client 4.0.0. Closes gh-30698 --- framework-platform/framework-platform.gradle | 5 +- spring-web/spring-web.gradle | 9 +- .../http/client/JettyClientHttpRequest.java | 8 +- .../client/JettyClientHttpRequestFactory.java | 2 +- .../http/client/JettyClientHttpResponse.java | 2 +- .../reactive/JettyClientHttpConnector.java | 297 +++++++++++++++++- .../reactive/JettyClientHttpRequest.java | 46 +-- .../client/reactive/JettyResourceFactory.java | 6 +- .../reactive/JettyHttpHandlerAdapter.java | 130 +------- .../http/support/JettyHeadersAdapter.java | 47 +-- .../WebRequestDataBinderIntegrationTests.java | 24 +- .../reactive/bootstrap/JettyHttpServer.java | 13 +- spring-webflux/spring-webflux.gradle | 4 +- .../adapter/JettyWebSocketHandlerAdapter.java | 14 +- .../socket/adapter/JettyWebSocketSession.java | 41 +-- .../socket/client/JettyWebSocketClient.java | 175 ----------- .../upgrade/JettyRequestUpgradeStrategy.java | 4 +- ...ractReactiveWebSocketIntegrationTests.java | 3 +- spring-webmvc/spring-webmvc.gradle | 5 +- .../RequestPartIntegrationTests.java | 26 +- spring-websocket/spring-websocket.gradle | 7 +- .../jetty/JettyWebSocketHandlerAdapter.java | 13 +- .../adapter/jetty/JettyWebSocketSession.java | 60 +++- .../client/jetty/JettyWebSocketClient.java | 174 ---------- .../web/socket/client/jetty/package-info.java | 9 - .../jetty/JettyRequestUpgradeStrategy.java | 4 +- .../sockjs/client/JettyXhrTransport.java | 12 +- .../AbstractWebSocketIntegrationTests.java | 3 +- .../web/socket/JettyWebSocketTestServer.java | 8 +- .../JettyWebSocketHandlerAdapterTests.java | 2 +- .../jetty/JettyWebSocketClientTests.java | 125 -------- .../client/JettySockJsIntegrationTests.java | 64 ---- 32 files changed, 491 insertions(+), 851 deletions(-) delete mode 100644 spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/JettyWebSocketClient.java delete mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java delete mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/package-info.java delete mode 100644 spring-websocket/src/test/java/org/springframework/web/socket/client/jetty/JettyWebSocketClientTests.java delete mode 100644 spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/JettySockJsIntegrationTests.java diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index 99dbf6b036..7a6fd2095f 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -15,7 +15,8 @@ dependencies { api(platform("io.rsocket:rsocket-bom:1.1.3")) api(platform("org.apache.groovy:groovy-bom:4.0.14")) api(platform("org.apache.logging.log4j:log4j-bom:2.20.0")) - api(platform("org.eclipse.jetty:jetty-bom:11.0.16")) + api(platform("org.eclipse.jetty:jetty-bom:12.0.1")) + api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.1")) api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3")) api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.0")) api(platform("org.junit:junit-bom:5.10.0")) @@ -115,7 +116,7 @@ dependencies { api("org.codehaus.jettison:jettison:1.5.4") api("org.crac:crac:1.3.0") api("org.dom4j:dom4j:2.1.4") - api("org.eclipse.jetty:jetty-reactive-httpclient:3.0.8") + api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.0") api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.3") api("org.eclipse:yasson:2.0.4") api("org.ehcache:ehcache:3.10.8") diff --git a/spring-web/spring-web.gradle b/spring-web/spring-web.gradle index d0823660f6..7d59e0ff40 100644 --- a/spring-web/spring-web.gradle +++ b/spring-web/spring-web.gradle @@ -48,10 +48,7 @@ dependencies { optional("org.eclipse.jetty:jetty-server") { exclude group: "jakarta.servlet", module: "jakarta.servlet-api" } - optional("org.eclipse.jetty:jetty-servlet") { - exclude group: "jakarta.servlet", module: "jakarta.servlet-api" - } - optional("org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.0.beta2") { + optional("org.eclipse.jetty.ee10:jetty-ee10-servlet") { exclude group: "jakarta.servlet", module: "jakarta.servlet-api" exclude group: "org.eclipse.jetty", module: "jetty-ee" exclude group: "org.eclipse.jetty", module: "jetty-security" @@ -73,7 +70,7 @@ dependencies { testFixturesImplementation("org.bouncycastle:bcpkix-jdk18on") { because("needed by Netty's SelfSignedCertificate on JDK 15+") } - testFixturesImplementation("org.eclipse.jetty.websocket:websocket-jetty-server") + testFixturesImplementation("org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-server") testImplementation(project(":spring-core-test")) testImplementation(testFixtures(project(":spring-beans"))) testImplementation(testFixtures(project(":spring-context"))) @@ -89,7 +86,7 @@ dependencies { testImplementation("org.apache.tomcat:tomcat-util") testImplementation("org.apache.tomcat.embed:tomcat-embed-core") testImplementation("org.eclipse.jetty:jetty-server") - testImplementation("org.eclipse.jetty:jetty-servlet") + testImplementation("org.eclipse.jetty.ee10:jetty-ee10-servlet") testImplementation("org.jetbrains.kotlin:kotlin-reflect") testImplementation("org.skyscreamer:jsonassert") testImplementation("org.xmlunit:xmlunit-assertj") diff --git a/spring-web/src/main/java/org/springframework/http/client/JettyClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/JettyClientHttpRequest.java index 06fe2b69d2..16c7546dfc 100644 --- a/spring-web/src/main/java/org/springframework/http/client/JettyClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/JettyClientHttpRequest.java @@ -24,10 +24,10 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.client.util.InputStreamResponseListener; -import org.eclipse.jetty.client.util.OutputStreamRequestContent; +import org.eclipse.jetty.client.InputStreamResponseListener; +import org.eclipse.jetty.client.OutputStreamRequestContent; +import org.eclipse.jetty.client.Request; +import org.eclipse.jetty.client.Response; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; diff --git a/spring-web/src/main/java/org/springframework/http/client/JettyClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/JettyClientHttpRequestFactory.java index f02b427819..b531d8b4dc 100644 --- a/spring-web/src/main/java/org/springframework/http/client/JettyClientHttpRequestFactory.java +++ b/spring-web/src/main/java/org/springframework/http/client/JettyClientHttpRequestFactory.java @@ -21,7 +21,7 @@ import java.net.URI; import java.time.Duration; import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.Request; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; diff --git a/spring-web/src/main/java/org/springframework/http/client/JettyClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/JettyClientHttpResponse.java index 5c59c05629..a2ec1d0be8 100644 --- a/spring-web/src/main/java/org/springframework/http/client/JettyClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/JettyClientHttpResponse.java @@ -19,7 +19,7 @@ package org.springframework.http.client; import java.io.IOException; import java.io.InputStream; -import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.client.Response; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatusCode; diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpConnector.java b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpConnector.java index 7bf5c55607..a2895f6ded 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpConnector.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpConnector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,18 +17,24 @@ package org.springframework.http.client.reactive; import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.IntPredicate; import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.reactive.client.ContentChunk; +import org.eclipse.jetty.client.Request; +import org.eclipse.jetty.io.Content; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.core.io.buffer.PooledDataBuffer; +import org.springframework.core.io.buffer.TouchableDataBuffer; import org.springframework.http.HttpMethod; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -133,19 +139,284 @@ public class JettyClientHttpConnector implements ClientHttpConnector { })); } - private DataBuffer toDataBuffer(ContentChunk chunk) { + private DataBuffer toDataBuffer(Content.Chunk chunk) { + DataBuffer delegate = this.bufferFactory.wrap(chunk.getByteBuffer()); + return new JettyDataBuffer(delegate, chunk); + } + + + private static final class JettyDataBuffer implements PooledDataBuffer { + + private final DataBuffer delegate; + + private final Content.Chunk chunk; + + private final AtomicInteger refCount = new AtomicInteger(1); + + + public JettyDataBuffer(DataBuffer delegate, Content.Chunk chunk) { + Assert.notNull(delegate, "Delegate must not be null"); + Assert.notNull(chunk, "Chunk must not be null"); + + this.delegate = delegate; + this.chunk = chunk; + } + + @Override + public boolean isAllocated() { + return this.refCount.get() > 0; + } + + @Override + public PooledDataBuffer retain() { + if (this.delegate instanceof PooledDataBuffer pooledDelegate) { + pooledDelegate.retain(); + } + this.chunk.retain(); + this.refCount.getAndUpdate(c -> { + if (c != 0) { + return c + 1; + } + else { + return 0; + } + }); + return this; + } + + @Override + public boolean release() { + if (this.delegate instanceof PooledDataBuffer pooledDelegate) { + pooledDelegate.release(); + } + this.chunk.release(); + int refCount = this.refCount.updateAndGet(c -> { + if (c != 0) { + return c - 1; + } + else { + throw new IllegalStateException("already released " + this); + } + }); + return refCount == 0; + } + + @Override + public PooledDataBuffer touch(Object hint) { + if (this.delegate instanceof TouchableDataBuffer touchableDelegate) { + touchableDelegate.touch(hint); + } + return this; + } + + // delegation + + @Override + public DataBufferFactory factory() { + return this.delegate.factory(); + } - // Originally we copy due to do: - // https://github.com/eclipse/jetty.project/issues/2429 + @Override + public int indexOf(IntPredicate predicate, int fromIndex) { + return this.delegate.indexOf(predicate, fromIndex); + } + + @Override + public int lastIndexOf(IntPredicate predicate, int fromIndex) { + return this.delegate.lastIndexOf(predicate, fromIndex); + } + + @Override + public int readableByteCount() { + return this.delegate.readableByteCount(); + } + + @Override + public int writableByteCount() { + return this.delegate.writableByteCount(); + } + + @Override + public int capacity() { + return this.delegate.capacity(); + } + + @Override + @Deprecated + public DataBuffer capacity(int capacity) { + this.delegate.capacity(capacity); + return this; + } + + @Override + public DataBuffer ensureWritable(int capacity) { + this.delegate.ensureWritable(capacity); + return this; + } + + @Override + public int readPosition() { + return this.delegate.readPosition(); + } + + @Override + public DataBuffer readPosition(int readPosition) { + this.delegate.readPosition(readPosition); + return this; + } + + @Override + public int writePosition() { + return this.delegate.writePosition(); + } + + @Override + public DataBuffer writePosition(int writePosition) { + this.delegate.writePosition(writePosition); + return this; + } - // Now that the issue is marked fixed we need to replace the below with a - // PooledDataBuffer that adapts "release()" to "succeeded()", and also - // evaluate if the concern here is addressed. + @Override + public byte getByte(int index) { + return this.delegate.getByte(index); + } + + @Override + public byte read() { + return this.delegate.read(); + } - DataBuffer buffer = this.bufferFactory.allocateBuffer(chunk.buffer.capacity()); - buffer.write(chunk.buffer); - chunk.callback.succeeded(); - return buffer; + @Override + public DataBuffer read(byte[] destination) { + this.delegate.read(destination); + return this; + } + + @Override + public DataBuffer read(byte[] destination, int offset, int length) { + this.delegate.read(destination, offset, length); + return this; + } + + @Override + public DataBuffer write(byte b) { + this.delegate.write(b); + return this; + } + + @Override + public DataBuffer write(byte[] source) { + this.delegate.write(source); + return this; + } + + @Override + public DataBuffer write(byte[] source, int offset, int length) { + this.delegate.write(source, offset, length); + return this; + } + + @Override + public DataBuffer write(DataBuffer... buffers) { + this.delegate.write(buffers); + return this; + } + + @Override + public DataBuffer write(ByteBuffer... buffers) { + this.delegate.write(buffers); + return this; + } + + @Override + @Deprecated + public DataBuffer slice(int index, int length) { + DataBuffer delegateSlice = this.delegate.slice(index, length); + this.chunk.retain(); + return new JettyDataBuffer(delegateSlice, this.chunk); + } + + @Override + public DataBuffer split(int index) { + DataBuffer delegateSplit = this.delegate.split(index); + this.chunk.retain(); + return new JettyDataBuffer(delegateSplit, this.chunk); + } + + @Override + @Deprecated + public ByteBuffer asByteBuffer() { + return this.delegate.asByteBuffer(); + } + + @Override + @Deprecated + public ByteBuffer asByteBuffer(int index, int length) { + return this.delegate.asByteBuffer(index, length); + } + + @Override + @Deprecated + public ByteBuffer toByteBuffer(int index, int length) { + return this.delegate.toByteBuffer(index, length); + } + + @Override + public void toByteBuffer(int srcPos, ByteBuffer dest, int destPos, int length) { + this.delegate.toByteBuffer(srcPos, dest, destPos, length); + } + + @Override + public ByteBufferIterator readableByteBuffers() { + ByteBufferIterator delegateIterator = this.delegate.readableByteBuffers(); + return new JettyByteBufferIterator(delegateIterator, this.chunk); + } + + @Override + public ByteBufferIterator writableByteBuffers() { + ByteBufferIterator delegateIterator = this.delegate.writableByteBuffers(); + return new JettyByteBufferIterator(delegateIterator, this.chunk); + } + + @Override + public String toString(int index, int length, Charset charset) { + return this.delegate.toString(index, length, charset); + } + + + private static final class JettyByteBufferIterator implements ByteBufferIterator { + + private final ByteBufferIterator delegate; + + private final Content.Chunk chunk; + + + public JettyByteBufferIterator(ByteBufferIterator delegate, Content.Chunk chunk) { + Assert.notNull(delegate, "Delegate must not be null"); + Assert.notNull(chunk, "Chunk must not be null"); + + this.delegate = delegate; + this.chunk = chunk; + this.chunk.retain(); + } + + + @Override + public void close() { + this.delegate.close(); + this.chunk.release(); + } + + @Override + public boolean hasNext() { + return this.delegate.hasNext(); + } + + @Override + public ByteBuffer next() { + return this.delegate.next(); + } + } } } diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpRequest.java index dac63c668c..d5e934c2bd 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpRequest.java @@ -16,20 +16,20 @@ package org.springframework.http.client.reactive; -import java.net.HttpCookie; import java.net.URI; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.function.Function; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.reactive.client.ContentChunk; +import org.eclipse.jetty.client.Request; +import org.eclipse.jetty.http.HttpCookie; +import org.eclipse.jetty.io.Content; import org.eclipse.jetty.reactive.client.ReactiveRequest; -import org.eclipse.jetty.util.Callback; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import reactor.core.publisher.MonoSink; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; @@ -92,7 +92,9 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest { public Mono writeWith(Publisher body) { return Mono.create(sink -> { ReactiveRequest.Content content = Flux.from(body) - .map(buffer -> toContentChunk(buffer, sink)) + .concatMapIterable(this::toContentChunks) + .concatWith(Mono.just(Content.Chunk.EOF)) + .doOnError(sink::error) .as(chunks -> ReactiveRequest.Content.fromPublisher(chunks, getContentType())); this.builder.content(content); sink.success(); @@ -111,26 +113,28 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest { return contentType != null ? contentType.toString() : MediaType.APPLICATION_OCTET_STREAM_VALUE; } - private ContentChunk toContentChunk(DataBuffer dataBuffer, MonoSink sink) { - ByteBuffer byteBuffer = ByteBuffer.allocate(dataBuffer.readableByteCount()); - dataBuffer.toByteBuffer(byteBuffer); - return new ContentChunk(byteBuffer, new Callback() { - @Override - public void succeeded() { - DataBufferUtils.release(dataBuffer); - } - @Override - public void failed(Throwable t) { - DataBufferUtils.release(dataBuffer); - sink.error(t); - } - }); + private List toContentChunks(DataBuffer dataBuffer) { + + List result = new ArrayList<>(1); + DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers(); + while (iterator.hasNext()) { + ByteBuffer byteBuffer = iterator.next(); + boolean last = !iterator.hasNext(); + Content.Chunk chunk = Content.Chunk.from(byteBuffer, false, () -> { + if (last) { + iterator.close(); + DataBufferUtils.release(dataBuffer); + } + }); + result.add(chunk); + } + return result; } @Override protected void applyCookies() { getCookies().values().stream().flatMap(Collection::stream) - .map(cookie -> new HttpCookie(cookie.getName(), cookie.getValue())) + .map(cookie -> HttpCookie.build(cookie.getName(), cookie.getValue()).build()) .forEach(this.jettyRequest::cookie); } diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyResourceFactory.java b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyResourceFactory.java index e0564dac82..5de7af8aa1 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyResourceFactory.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyResourceFactory.java @@ -20,8 +20,8 @@ package org.springframework.http.client.reactive; import java.nio.ByteBuffer; import java.util.concurrent.Executor; +import org.eclipse.jetty.io.ArrayByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool; -import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.util.ProcessorUtils; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.thread.QueuedThreadPool; @@ -69,7 +69,7 @@ public class JettyResourceFactory implements InitializingBean, DisposableBean { /** * Configure the {@link ByteBufferPool} to use. - *

By default, initialized with a {@link MappedByteBufferPool}. + *

By default, initialized with a {@link ArrayByteBufferPool}. * @param byteBufferPool the {@link ByteBuffer} pool to use */ public void setByteBufferPool(@Nullable ByteBufferPool byteBufferPool) { @@ -130,7 +130,7 @@ public class JettyResourceFactory implements InitializingBean, DisposableBean { this.executor = threadPool; } if (this.byteBufferPool == null) { - this.byteBufferPool = new MappedByteBufferPool(2048, + this.byteBufferPool = new ArrayByteBufferPool(0, 2048, 65536, // from HttpClient:202 this.executor instanceof ThreadPool.SizedThreadPool sizedThreadPool ? sizedThreadPool.getMaxThreads() / 2 : ProcessorUtils.availableProcessors() * 2); diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java index 5742e210d5..b6240d2553 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java @@ -18,26 +18,14 @@ package org.springframework.http.server.reactive; import java.io.IOException; import java.io.OutputStream; -import java.net.URISyntaxException; import java.nio.ByteBuffer; import jakarta.servlet.AsyncContext; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletRequestWrapper; import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpServletResponseWrapper; -import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.server.HttpOutput; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.ee10.servlet.HttpOutput; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; -import org.springframework.http.HttpHeaders; -import org.springframework.http.support.JettyHeadersAdapter; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.MultiValueMap; /** * {@link ServletHttpHandlerAdapter} extension that uses Jetty APIs for writing @@ -51,128 +39,18 @@ import org.springframework.util.MultiValueMap; */ public class JettyHttpHandlerAdapter extends ServletHttpHandlerAdapter { - private static final boolean jetty11Present = ClassUtils.isPresent( - "org.eclipse.jetty.server.HttpOutput", JettyHttpHandlerAdapter.class.getClassLoader()); - - private static final boolean jetty12Present = ClassUtils.isPresent( - "org.eclipse.jetty.ee10.servlet.HttpOutput", JettyHttpHandlerAdapter.class.getClassLoader()); - public JettyHttpHandlerAdapter(HttpHandler httpHandler) { super(httpHandler); } - @Override - protected ServletServerHttpRequest createRequest(HttpServletRequest request, AsyncContext context) - throws IOException, URISyntaxException { - - if (jetty11Present) { - Assert.state(getServletPath() != null, "Servlet path is not initialized"); - return new Jetty11ServerHttpRequest( - request, context, getServletPath(), getDataBufferFactory(), getBufferSize()); - } - else { - return super.createRequest(request, context); - } - } - @Override protected ServletServerHttpResponse createResponse(HttpServletResponse response, AsyncContext context, ServletServerHttpRequest request) throws IOException { - if (jetty11Present) { - return new Jetty11ServerHttpResponse( - response, context, getDataBufferFactory(), getBufferSize(), request); - } - else if (jetty12Present) { - return new Jetty12ServerHttpResponse( - response, context, getDataBufferFactory(), getBufferSize(), request); - } - else { - return super.createResponse(response, context, request); - } - } - - - private static final class Jetty11ServerHttpRequest extends ServletServerHttpRequest { - - Jetty11ServerHttpRequest(HttpServletRequest request, AsyncContext asyncContext, - String servletPath, DataBufferFactory bufferFactory, int bufferSize) - throws IOException, URISyntaxException { - - super(createHeaders(request), request, asyncContext, servletPath, bufferFactory, bufferSize); - } - - private static MultiValueMap createHeaders(HttpServletRequest servletRequest) { - Request request = getRequest(servletRequest); - return new JettyHeadersAdapter(HttpFields.build(request.getHttpFields())); - } - - private static Request getRequest(HttpServletRequest request) { - if (request instanceof Request jettyRequest) { - return jettyRequest; - } - else if (request instanceof HttpServletRequestWrapper wrapper) { - HttpServletRequest wrappedRequest = (HttpServletRequest) wrapper.getRequest(); - return getRequest(wrappedRequest); - } - else { - throw new IllegalArgumentException("Cannot convert [" + request.getClass() + - "] to org.eclipse.jetty.server.Request"); - } - } - } - - - private static final class Jetty11ServerHttpResponse extends ServletServerHttpResponse { - - Jetty11ServerHttpResponse(HttpServletResponse response, AsyncContext asyncContext, - DataBufferFactory bufferFactory, int bufferSize, ServletServerHttpRequest request) - throws IOException { - - super(createHeaders(response), response, asyncContext, bufferFactory, bufferSize, request); - } - - private static HttpHeaders createHeaders(HttpServletResponse servletResponse) { - Response response = getResponse(servletResponse); - return new HttpHeaders(new JettyHeadersAdapter(response.getHttpFields())); - } - - private static Response getResponse(HttpServletResponse response) { - if (response instanceof Response jettyResponse) { - return jettyResponse; - } - else if (response instanceof HttpServletResponseWrapper wrapper) { - HttpServletResponse wrappedResponse = (HttpServletResponse) wrapper.getResponse(); - return getResponse(wrappedResponse); - } - else { - throw new IllegalArgumentException("Cannot convert [" + response.getClass() + - "] to org.eclipse.jetty.server.Response"); - } - } - - @Override - protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException { - if (getOutputStream() instanceof HttpOutput httpOutput) { - int len = 0; - try (DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers()) { - while (iterator.hasNext() && httpOutput.isReady()) { - ByteBuffer byteBuffer = iterator.next(); - len += byteBuffer.remaining(); - httpOutput.write(byteBuffer); - } - } - return len; - } - return super.writeToOutputStream(dataBuffer); - } - - @Override - protected void applyHeaders() { - adaptHeaders(false); - } + return new Jetty12ServerHttpResponse( + response, context, getDataBufferFactory(), getBufferSize(), request); } @@ -188,7 +66,7 @@ public class JettyHttpHandlerAdapter extends ServletHttpHandlerAdapter { @Override protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException { OutputStream output = getOutputStream(); - if (output instanceof org.eclipse.jetty.ee10.servlet.HttpOutput httpOutput) { + if (output instanceof HttpOutput httpOutput) { int len = 0; try (DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers()) { while (iterator.hasNext() && httpOutput.isReady()) { diff --git a/spring-web/src/main/java/org/springframework/http/support/JettyHeadersAdapter.java b/spring-web/src/main/java/org/springframework/http/support/JettyHeadersAdapter.java index 9a7da7170f..57f9b7ab89 100644 --- a/spring-web/src/main/java/org/springframework/http/support/JettyHeadersAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/support/JettyHeadersAdapter.java @@ -44,8 +44,6 @@ public final class JettyHeadersAdapter implements MultiValueMap private final HttpFields headers; - private static final String IMMUTABLE_HEADER_ERROR = "Immutable headers"; - /** * Creates a new {@code JettyHeadersAdapter} based on the given @@ -65,10 +63,10 @@ public final class JettyHeadersAdapter implements MultiValueMap @Override public void add(String key, @Nullable String value) { - if (!(this.headers instanceof HttpFields.Mutable mutableHttpFields)) { - throw new IllegalStateException(IMMUTABLE_HEADER_ERROR); + if (value != null) { + HttpFields.Mutable mutableHttpFields = mutableFields(); + mutableHttpFields.add(key, value); } - mutableHttpFields.add(key, value); } @Override @@ -83,10 +81,13 @@ public final class JettyHeadersAdapter implements MultiValueMap @Override public void set(String key, @Nullable String value) { - if (!(this.headers instanceof HttpFields.Mutable mutableHttpFields)) { - throw new IllegalStateException(IMMUTABLE_HEADER_ERROR); + HttpFields.Mutable mutableHttpFields = mutableFields(); + if (value != null) { + mutableHttpFields.put(key, value); + } + else { + mutableHttpFields.remove(key); } - mutableHttpFields.put(key, value); } @Override @@ -139,9 +140,7 @@ public final class JettyHeadersAdapter implements MultiValueMap @Nullable @Override public List put(String key, List value) { - if (!(this.headers instanceof HttpFields.Mutable mutableHttpFields)) { - throw new IllegalStateException(IMMUTABLE_HEADER_ERROR); - } + HttpFields.Mutable mutableHttpFields = mutableFields(); List oldValues = get(key); mutableHttpFields.put(key, value); return oldValues; @@ -150,9 +149,7 @@ public final class JettyHeadersAdapter implements MultiValueMap @Nullable @Override public List remove(Object key) { - if (!(this.headers instanceof HttpFields.Mutable mutableHttpFields)) { - throw new IllegalStateException(IMMUTABLE_HEADER_ERROR); - } + HttpFields.Mutable mutableHttpFields = mutableFields(); if (key instanceof String name) { List oldValues = get(key); mutableHttpFields.remove(name); @@ -168,9 +165,7 @@ public final class JettyHeadersAdapter implements MultiValueMap @Override public void clear() { - if (!(this.headers instanceof HttpFields.Mutable mutableHttpFields)) { - throw new IllegalStateException(IMMUTABLE_HEADER_ERROR); - } + HttpFields.Mutable mutableHttpFields = mutableFields(); mutableHttpFields.clear(); } @@ -199,6 +194,16 @@ public final class JettyHeadersAdapter implements MultiValueMap }; } + private HttpFields.Mutable mutableFields() { + if (this.headers instanceof HttpFields.Mutable mutableHttpFields) { + return mutableHttpFields; + } + else { + throw new IllegalStateException("Immutable headers"); + } + } + + @Override public String toString() { @@ -242,9 +247,7 @@ public final class JettyHeadersAdapter implements MultiValueMap @Override public List setValue(List value) { - if (!(headers instanceof HttpFields.Mutable mutableHttpFields)) { - throw new IllegalStateException(IMMUTABLE_HEADER_ERROR); - } + HttpFields.Mutable mutableHttpFields = mutableFields(); List previousValues = headers.getValuesList(this.key); mutableHttpFields.put(this.key, value); return previousValues; @@ -290,9 +293,7 @@ public final class JettyHeadersAdapter implements MultiValueMap @Override public void remove() { - if (!(headers instanceof HttpFields.Mutable mutableHttpFields)) { - throw new IllegalStateException(IMMUTABLE_HEADER_ERROR); - } + HttpFields.Mutable mutableHttpFields = mutableFields(); if (this.currentName == null) { throw new IllegalStateException("No current Header in iterator"); } diff --git a/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderIntegrationTests.java index bf98d5de80..73f47d1680 100644 --- a/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.web.bind.support; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; import jakarta.servlet.MultipartConfigElement; @@ -23,11 +25,11 @@ import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.Part; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.servlet.ServletHolder; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.NetworkConnector; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -37,6 +39,7 @@ import org.junit.jupiter.api.TestInstance.Lifecycle; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.util.FileSystemUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; @@ -61,6 +64,8 @@ class WebRequestDataBinderIntegrationTests { private String baseUrl; + private Path tempDirectory; + @BeforeAll void startJettyServer() throws Exception { @@ -69,7 +74,9 @@ class WebRequestDataBinderIntegrationTests { ServletContextHandler handler = new ServletContextHandler(); - MultipartConfigElement multipartConfig = new MultipartConfigElement(""); + this.tempDirectory = Files.createTempDirectory("WebRequestDataBinderIntegrationTests"); + + MultipartConfigElement multipartConfig = new MultipartConfigElement(this.tempDirectory.toString()); ServletHolder holder = new ServletHolder(partsServlet); holder.getRegistration().setMultipartConfig(multipartConfig); @@ -89,8 +96,13 @@ class WebRequestDataBinderIntegrationTests { @AfterAll void stopJettyServer() throws Exception { - if (jettyServer != null) { - jettyServer.stop(); + try { + if (jettyServer != null) { + jettyServer.stop(); + } + } + finally { + FileSystemUtils.deleteRecursively(this.tempDirectory); } } diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/JettyHttpServer.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/JettyHttpServer.java index 350566c587..d3912ac2df 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/JettyHttpServer.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/JettyHttpServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,11 @@ package org.springframework.web.testfixture.http.server.reactive.bootstrap; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.servlet.ServletHolder; +import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.springframework.http.server.reactive.JettyHttpHandlerAdapter; import org.springframework.http.server.reactive.ServletHttpHandlerAdapter; @@ -45,15 +45,16 @@ public class JettyHttpServer extends AbstractHttpServer { ServletHolder servletHolder = new ServletHolder(servlet); servletHolder.setAsyncSupported(true); - this.contextHandler = new ServletContextHandler(this.jettyServer, "", false, false); + this.contextHandler = new ServletContextHandler("", false, false); this.contextHandler.addServlet(servletHolder, "/"); this.contextHandler.addServletContainerInitializer(new JettyWebSocketServletContainerInitializer()); - this.contextHandler.start(); ServerConnector connector = new ServerConnector(this.jettyServer); connector.setHost(getHost()); connector.setPort(getPort()); this.jettyServer.addConnector(connector); + this.jettyServer.setHandler(this.contextHandler); + this.contextHandler.start(); } private ServletHttpHandlerAdapter createServletAdapter() { diff --git a/spring-webflux/spring-webflux.gradle b/spring-webflux/spring-webflux.gradle index a6dac2569d..e0f98bcdc8 100644 --- a/spring-webflux/spring-webflux.gradle +++ b/spring-webflux/spring-webflux.gradle @@ -24,8 +24,7 @@ dependencies { exclude group: "org.apache.tomcat", module: "tomcat-servlet-api" exclude group: "org.apache.tomcat", module: "tomcat-websocket-api" } - optional("org.eclipse.jetty.websocket:websocket-jetty-client") - optional("org.eclipse.jetty.websocket:websocket-jetty-server") { + optional("org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-server") { exclude group: "jakarta.servlet", module: "jakarta.servlet-api" } optional("org.freemarker:freemarker") @@ -50,7 +49,6 @@ dependencies { testImplementation("org.apache.tomcat.embed:tomcat-embed-core") testImplementation("org.eclipse.jetty:jetty-reactive-httpclient") testImplementation("org.eclipse.jetty:jetty-server") - testImplementation("org.eclipse.jetty:jetty-servlet") testImplementation("org.hibernate:hibernate-validator") testImplementation("org.jetbrains.kotlin:kotlin-script-runtime") testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json") diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/JettyWebSocketHandlerAdapter.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/JettyWebSocketHandlerAdapter.java index 79f750abd7..bddb3be54b 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/JettyWebSocketHandlerAdapter.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/JettyWebSocketHandlerAdapter.java @@ -20,13 +20,14 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.function.Function; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Frame; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.core.OpCode; @@ -71,8 +72,8 @@ public class JettyWebSocketHandlerAdapter { } - @OnWebSocketConnect - public void onWebSocketConnect(Session session) { + @OnWebSocketOpen + public void onWebSocketOpen(Session session) { this.delegateSession = this.sessionFactory.apply(session); this.delegateHandler.handle(this.delegateSession) .checkpoint(session.getUpgradeRequest().getRequestURI() + " [JettyWebSocketHandlerAdapter]") @@ -88,21 +89,22 @@ public class JettyWebSocketHandlerAdapter { } @OnWebSocketMessage - public void onWebSocketBinary(byte[] message, int offset, int length) { + public void onWebSocketBinary(ByteBuffer buffer, Callback callback) { if (this.delegateSession != null) { - ByteBuffer buffer = ByteBuffer.wrap(message, offset, length); WebSocketMessage webSocketMessage = toMessage(Type.BINARY, buffer); this.delegateSession.handleMessage(webSocketMessage.getType(), webSocketMessage); + callback.succeed(); } } @OnWebSocketFrame - public void onWebSocketFrame(Frame frame) { + public void onWebSocketFrame(Frame frame, Callback callback) { if (this.delegateSession != null) { if (OpCode.PONG == frame.getOpCode()) { ByteBuffer buffer = (frame.getPayload() != null ? frame.getPayload() : EMPTY_PAYLOAD); WebSocketMessage webSocketMessage = toMessage(Type.PONG, buffer); this.delegateSession.handleMessage(webSocketMessage.getType(), webSocketMessage); + callback.succeed(); } } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/JettyWebSocketSession.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/JettyWebSocketSession.java index 78412ddc55..d6adca7e92 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/JettyWebSocketSession.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/JettyWebSocketSession.java @@ -20,17 +20,14 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import org.eclipse.jetty.websocket.api.RemoteEndpoint; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.SuspendToken; -import org.eclipse.jetty.websocket.api.WriteCallback; import reactor.core.publisher.Mono; import reactor.core.publisher.Sinks; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.lang.Nullable; -import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.reactive.socket.CloseStatus; import org.springframework.web.reactive.socket.HandshakeInfo; @@ -47,10 +44,6 @@ import org.springframework.web.reactive.socket.WebSocketSession; */ public class JettyWebSocketSession extends AbstractListenerWebSocketSession { - @Nullable - private volatile SuspendToken suspendToken; - - public JettyWebSocketSession(Session session, HandshakeInfo info, DataBufferFactory factory) { this(session, info, factory, null); } @@ -66,32 +59,26 @@ public class JettyWebSocketSession extends AbstractListenerWebSocketSession remote.sendBytes(byteBuffer, new SendProcessorCallback()); - case PING -> remote.sendPing(byteBuffer); - case PONG -> remote.sendPong(byteBuffer); + case BINARY -> session.sendBinary(byteBuffer, new SendProcessorCallback()); + case PING -> session.sendPing(byteBuffer, new SendProcessorCallback()); + case PONG -> session.sendPong(byteBuffer, new SendProcessorCallback()); default -> throw new IllegalArgumentException("Unexpected message type: " + message.getType()); } } @@ -119,21 +106,23 @@ public class JettyWebSocketSession extends AbstractListenerWebSocketSession close(CloseStatus status) { - getDelegate().close(status.getCode(), status.getReason()); - return Mono.empty(); + Callback.Completable callback = new Callback.Completable(); + getDelegate().close(status.getCode(), status.getReason(), callback); + + return Mono.fromFuture(callback); } - private final class SendProcessorCallback implements WriteCallback { + private final class SendProcessorCallback implements Callback { @Override - public void writeFailed(Throwable x) { + public void fail(Throwable x) { getSendProcessor().cancel(); getSendProcessor().onError(x); } @Override - public void writeSuccess() { + public void succeed() { getSendProcessor().setReadyToSend(true); getSendProcessor().onWritePossible(); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/JettyWebSocketClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/JettyWebSocketClient.java deleted file mode 100644 index 94264479c9..0000000000 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/JettyWebSocketClient.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2002-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.web.reactive.socket.client; - -import java.io.IOException; -import java.net.URI; -import java.util.function.Function; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Sinks; - -import org.springframework.context.Lifecycle; -import org.springframework.core.io.buffer.DefaultDataBufferFactory; -import org.springframework.http.HttpHeaders; -import org.springframework.web.reactive.socket.HandshakeInfo; -import org.springframework.web.reactive.socket.WebSocketHandler; -import org.springframework.web.reactive.socket.adapter.ContextWebSocketHandler; -import org.springframework.web.reactive.socket.adapter.JettyWebSocketHandlerAdapter; -import org.springframework.web.reactive.socket.adapter.JettyWebSocketSession; - -/** - * A {@link WebSocketClient} implementation for use with Jetty - * {@link org.eclipse.jetty.websocket.client.WebSocketClient}. - * Only supported on Jetty 11, superseded by {@link StandardWebSocketClient}. - * - *

Note: the Jetty {@code WebSocketClient} requires - * lifecycle management and must be started and stopped. This is automatically - * managed when this class is declared as a Spring bean and created with the - * default constructor. See constructor notes for more details. - * - * @author Violeta Georgieva - * @author Rossen Stoyanchev - * @author Juergen Hoeller - * @since 5.0 - * @deprecated as of 6.0.3, in favor of {@link StandardWebSocketClient} - */ -@Deprecated(since = "6.0.3", forRemoval = true) -public class JettyWebSocketClient implements WebSocketClient, Lifecycle { - - private static final Log logger = LogFactory.getLog(JettyWebSocketClient.class); - - private final org.eclipse.jetty.websocket.client.WebSocketClient jettyClient; - - private final boolean externallyManaged; - - - /** - * Default constructor that creates and manages an instance of a Jetty - * {@link org.eclipse.jetty.websocket.client.WebSocketClient WebSocketClient}. - * The instance can be obtained with {@link #getJettyClient()} for further - * configuration. - *

Note: When this constructor is used {@link Lifecycle} - * methods of this class are delegated to the Jetty {@code WebSocketClient}. - */ - public JettyWebSocketClient() { - this.jettyClient = new org.eclipse.jetty.websocket.client.WebSocketClient(); - this.externallyManaged = false; - } - - /** - * Constructor that accepts an existing instance of a Jetty - * {@link org.eclipse.jetty.websocket.client.WebSocketClient WebSocketClient}. - *

Note: Use of this constructor implies the Jetty - * {@code WebSocketClient} is externally managed and hence {@link Lifecycle} - * methods of this class are not delegated to it. - */ - public JettyWebSocketClient(org.eclipse.jetty.websocket.client.WebSocketClient jettyClient) { - this.jettyClient = jettyClient; - this.externallyManaged = true; - } - - - /** - * Return the underlying Jetty {@code WebSocketClient}. - */ - public org.eclipse.jetty.websocket.client.WebSocketClient getJettyClient() { - return this.jettyClient; - } - - - @Override - public void start() { - if (!this.externallyManaged) { - try { - this.jettyClient.start(); - } - catch (Exception ex) { - throw new IllegalStateException("Failed to start Jetty WebSocketClient", ex); - } - } - } - - @Override - public void stop() { - if (!this.externallyManaged) { - try { - this.jettyClient.stop(); - } - catch (Exception ex) { - throw new IllegalStateException("Error stopping Jetty WebSocketClient", ex); - } - } - } - - @Override - public boolean isRunning() { - return this.jettyClient.isRunning(); - } - - - @Override - public Mono execute(URI url, WebSocketHandler handler) { - return execute(url, new HttpHeaders(), handler); - } - - @Override - public Mono execute(URI url, HttpHeaders headers, WebSocketHandler handler) { - return executeInternal(url, headers, handler); - } - - private Mono executeInternal(URI url, HttpHeaders headers, WebSocketHandler handler) { - Sinks.Empty completionSink = Sinks.empty(); - return Mono.deferContextual(contextView -> { - if (logger.isDebugEnabled()) { - logger.debug("Connecting to " + url); - } - Object jettyHandler = createHandler( - url, ContextWebSocketHandler.decorate(handler, contextView), completionSink); - ClientUpgradeRequest request = new ClientUpgradeRequest(); - request.setHeaders(headers); - request.setSubProtocols(handler.getSubProtocols()); - try { - this.jettyClient.connect(jettyHandler, url, request); - return completionSink.asMono(); - } - catch (IOException ex) { - return Mono.error(ex); - } - }); - } - - private Object createHandler(URI url, WebSocketHandler handler, Sinks.Empty completion) { - Function sessionFactory = session -> { - HandshakeInfo info = createHandshakeInfo(url, session); - return new JettyWebSocketSession(session, info, DefaultDataBufferFactory.sharedInstance, completion); - }; - return new JettyWebSocketHandlerAdapter(handler, sessionFactory); - } - - private HandshakeInfo createHandshakeInfo(URI url, Session jettySession) { - HttpHeaders headers = new HttpHeaders(); - headers.putAll(jettySession.getUpgradeResponse().getHeaders()); - String protocol = headers.getFirst("Sec-WebSocket-Protocol"); - return new HandshakeInfo(url, headers, Mono.empty(), protocol); - } - -} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/JettyRequestUpgradeStrategy.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/JettyRequestUpgradeStrategy.java index 7f56ca7565..b0072348bd 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/JettyRequestUpgradeStrategy.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/JettyRequestUpgradeStrategy.java @@ -21,8 +21,8 @@ import java.util.function.Supplier; import jakarta.servlet.ServletContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.websocket.server.JettyWebSocketCreator; -import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer; +import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketCreator; +import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServerContainer; import reactor.core.publisher.Mono; import org.springframework.core.io.buffer.DataBufferFactory; diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/socket/AbstractReactiveWebSocketIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/socket/AbstractReactiveWebSocketIntegrationTests.java index 601642c3c5..c604fc5b99 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/socket/AbstractReactiveWebSocketIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/socket/AbstractReactiveWebSocketIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -93,7 +93,6 @@ abstract class AbstractReactiveWebSocketIntegrationTests { @SuppressWarnings("removal") WebSocketClient[] clients = new WebSocketClient[] { new TomcatWebSocketClient(), - new org.springframework.web.reactive.socket.client.JettyWebSocketClient(), new ReactorNettyWebSocketClient(), new ReactorNetty2WebSocketClient(), new UndertowWebSocketClient(Xnio.getInstance().createWorker(OptionMap.EMPTY)) diff --git a/spring-webmvc/spring-webmvc.gradle b/spring-webmvc/spring-webmvc.gradle index 8547091b8f..30e7567821 100644 --- a/spring-webmvc/spring-webmvc.gradle +++ b/spring-webmvc/spring-webmvc.gradle @@ -55,10 +55,7 @@ dependencies { exclude group: "pull-parser", module: "pull-parser" exclude group: "xpp3", module: "xpp3" } - testImplementation("org.eclipse.jetty:jetty-server") { - exclude group: "jakarta.servlet", module: "jakarta.servlet-api" - } - testImplementation("org.eclipse.jetty:jetty-servlet") { + testImplementation("org.eclipse.jetty.ee10:jetty-ee10-servlet") { exclude group: "jakarta.servlet", module: "jakarta.servlet-api" } testImplementation("org.hibernate:hibernate-validator") diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartIntegrationTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartIntegrationTests.java index c54514bd66..0f385345f2 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartIntegrationTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ package org.springframework.web.servlet.mvc.method.annotation; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -25,11 +27,11 @@ import java.util.Map; import java.util.Optional; import jakarta.servlet.MultipartConfigElement; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.servlet.ServletHolder; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.NetworkConnector; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -51,6 +53,7 @@ import org.springframework.http.converter.ResourceHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; import org.springframework.stereotype.Controller; +import org.springframework.util.FileSystemUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MimeTypeUtils; import org.springframework.util.MultiValueMap; @@ -83,18 +86,22 @@ class RequestPartIntegrationTests { private static String baseUrl; + private static Path tempDirectory; + @BeforeAll static void startServer() throws Exception { // Let server pick its own random, available port. server = new Server(0); + tempDirectory = Files.createTempDirectory("RequestPartIntegrationTests"); + ServletContextHandler handler = new ServletContextHandler(); handler.setContextPath("/"); ServletHolder standardResolverServlet = new ServletHolder(DispatcherServlet.class); standardResolverServlet.setInitParameter("contextConfigLocation", StandardMultipartResolverTestConfig.class.getName()); standardResolverServlet.setInitParameter("contextClass", AnnotationConfigWebApplicationContext.class.getName()); - standardResolverServlet.getRegistration().setMultipartConfig(new MultipartConfigElement("")); + standardResolverServlet.getRegistration().setMultipartConfig(new MultipartConfigElement(tempDirectory.toString())); handler.addServlet(standardResolverServlet, "/standard-resolver/*"); server.setHandler(handler); @@ -107,8 +114,13 @@ class RequestPartIntegrationTests { @AfterAll static void stopServer() throws Exception { - if (server != null) { - server.stop(); + try { + if (server != null) { + server.stop(); + } + } + finally { + FileSystemUtils.deleteRecursively(tempDirectory); } } @@ -149,7 +161,7 @@ class RequestPartIntegrationTests { "Content-Length: 7\r\n" + "\r\n" + "content\r\n" + - "--" + boundaryText + "--"; + "--" + boundaryText + "--\r\n "; RequestEntity requestEntity = RequestEntity.post(URI.create(baseUrl + "/standard-resolver/spr13319")) diff --git a/spring-websocket/spring-websocket.gradle b/spring-websocket/spring-websocket.gradle index 20f466748e..72df03b20d 100644 --- a/spring-websocket/spring-websocket.gradle +++ b/spring-websocket/spring-websocket.gradle @@ -16,14 +16,13 @@ dependencies { exclude group: "org.apache.tomcat", module: "tomcat-servlet-api" exclude group: "org.apache.tomcat", module: "tomcat-websocket-api" } - optional("org.eclipse.jetty:jetty-webapp") { + optional("org.eclipse.jetty.ee10:jetty-ee10-webapp") { exclude group: "jakarta.servlet", module: "jakarta.servlet-api" } - optional("org.eclipse.jetty.websocket:websocket-jetty-server") { + optional("org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-server") + optional("org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-server") { exclude group: "jakarta.servlet", module: "jakarta.servlet-api" } - optional("org.eclipse.jetty.websocket:websocket-jetty-client") - optional("org.eclipse.jetty:jetty-client") optional("org.glassfish.tyrus:tyrus-container-servlet") testImplementation(testFixtures(project(":spring-core"))) testImplementation(testFixtures(project(":spring-web"))) diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketHandlerAdapter.java index 47b821cd53..1f10155ceb 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketHandlerAdapter.java @@ -20,13 +20,14 @@ import java.nio.ByteBuffer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Frame; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.core.OpCode; @@ -65,8 +66,8 @@ public class JettyWebSocketHandlerAdapter { } - @OnWebSocketConnect - public void onWebSocketConnect(Session session) { + @OnWebSocketOpen + public void onWebSocketOpen(Session session) { try { this.wsSession.initializeNativeSession(session); this.webSocketHandler.afterConnectionEstablished(this.wsSession); @@ -88,8 +89,8 @@ public class JettyWebSocketHandlerAdapter { } @OnWebSocketMessage - public void onWebSocketBinary(byte[] payload, int offset, int length) { - BinaryMessage message = new BinaryMessage(payload, offset, length, true); + public void onWebSocketBinary(ByteBuffer payload, Callback callback) { + BinaryMessage message = new BinaryMessage(payload, true); try { this.webSocketHandler.handleMessage(this.wsSession, message); } @@ -99,7 +100,7 @@ public class JettyWebSocketHandlerAdapter { } @OnWebSocketFrame - public void onWebSocketFrame(Frame frame) { + public void onWebSocketFrame(Frame frame, Callback callback) { if (OpCode.PONG == frame.getOpCode()) { ByteBuffer payload = frame.getPayload() != null ? frame.getPayload() : EMPTY_PAYLOAD; PongMessage message = new PongMessage(payload); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketSession.java index 880485d3a1..b3431bdd56 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketSession.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.web.socket.adapter.jetty; import java.io.IOException; +import java.io.UncheckedIOException; import java.net.InetSocketAddress; import java.net.URI; import java.security.Principal; @@ -24,9 +25,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutionException; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.ExtensionConfig; -import org.eclipse.jetty.websocket.api.RemoteEndpoint; import org.eclipse.jetty.websocket.api.Session; import org.springframework.http.HttpHeaders; @@ -131,18 +133,17 @@ public class JettyWebSocketSession extends AbstractWebSocketSession { @Override public InetSocketAddress getLocalAddress() { checkNativeSessionInitialized(); - return (InetSocketAddress) getNativeSession().getLocalAddress(); + return (InetSocketAddress) getNativeSession().getLocalSocketAddress(); } @Override public InetSocketAddress getRemoteAddress() { checkNativeSessionInitialized(); - return (InetSocketAddress) getNativeSession().getRemoteAddress(); + return (InetSocketAddress) getNativeSession().getRemoteSocketAddress(); } /** - * This method is a no-op for Jetty. As per {@link Session#getPolicy()}, the - * returned {@code WebSocketPolicy} is read-only and changing it has no effect. + * This method is a no-op for Jetty. */ @Override public void setTextMessageSizeLimit(int messageSizeLimit) { @@ -155,8 +156,7 @@ public class JettyWebSocketSession extends AbstractWebSocketSession { } /** - * This method is a no-op for Jetty. As per {@link Session#getPolicy()}, the - * returned {@code WebSocketPolicy} is read-only and changing it has no effect. + * This method is a no-op for Jetty. */ @Override public void setBinaryMessageSizeLimit(int messageSizeLimit) { @@ -210,31 +210,57 @@ public class JettyWebSocketSession extends AbstractWebSocketSession { @Override protected void sendTextMessage(TextMessage message) throws IOException { - getRemoteEndpoint().sendString(message.getPayload()); + useSession((session, callback) -> session.sendText(message.getPayload(), callback)); } @Override protected void sendBinaryMessage(BinaryMessage message) throws IOException { - getRemoteEndpoint().sendBytes(message.getPayload()); + useSession((session, callback) -> session.sendBinary(message.getPayload(), callback)); } @Override protected void sendPingMessage(PingMessage message) throws IOException { - getRemoteEndpoint().sendPing(message.getPayload()); + useSession((session, callback) -> session.sendPing(message.getPayload(), callback)); } @Override protected void sendPongMessage(PongMessage message) throws IOException { - getRemoteEndpoint().sendPong(message.getPayload()); - } - - private RemoteEndpoint getRemoteEndpoint() { - return getNativeSession().getRemote(); + useSession((session, callback) -> session.sendPong(message.getPayload(), callback)); } @Override protected void closeInternal(CloseStatus status) throws IOException { - getNativeSession().close(status.getCode(), status.getReason()); + useSession((session, callback) -> session.close(status.getCode(), status.getReason(), callback)); + } + + private void useSession(SessionConsumer sessionConsumer) throws IOException { + try { + Callback.Completable completable = new Callback.Completable(); + sessionConsumer.consume(getNativeSession(), completable); + completable.get(); + } + catch (ExecutionException ex) { + Throwable cause = ex.getCause(); + + if (cause instanceof IOException ioEx) { + throw ioEx; + } + else if (cause instanceof UncheckedIOException uioEx) { + throw uioEx.getCause(); + } + else { + throw new IOException(ex.getMessage(), cause); + } + } + catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + + @FunctionalInterface + private interface SessionConsumer { + + void consume(Session session, Callback callback) throws IOException; } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java deleted file mode 100644 index c893ba9bae..0000000000 --- a/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2002-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.web.socket.client.jetty; - -import java.net.URI; -import java.security.Principal; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.client.WebSocketClient; - -import org.springframework.context.Lifecycle; -import org.springframework.core.task.AsyncTaskExecutor; -import org.springframework.core.task.SimpleAsyncTaskExecutor; -import org.springframework.http.HttpHeaders; -import org.springframework.lang.Nullable; -import org.springframework.util.concurrent.FutureUtils; -import org.springframework.web.socket.WebSocketExtension; -import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.WebSocketSession; -import org.springframework.web.socket.adapter.jetty.JettyWebSocketHandlerAdapter; -import org.springframework.web.socket.adapter.jetty.JettyWebSocketSession; -import org.springframework.web.socket.adapter.jetty.WebSocketToJettyExtensionConfigAdapter; -import org.springframework.web.socket.client.AbstractWebSocketClient; - -/** - * Initiates WebSocket requests to a WebSocket server programmatically - * through the Jetty WebSocket API. Only supported on Jetty 11, superseded by - * {@link org.springframework.web.socket.client.standard.StandardWebSocketClient}. - * - *

As of 4.1 this class implements {@link Lifecycle} rather than - * {@link org.springframework.context.SmartLifecycle}. Use - * {@link org.springframework.web.socket.client.WebSocketConnectionManager - * WebSocketConnectionManager} instead to auto-start a WebSocket connection. - * - * @author Rossen Stoyanchev - * @author Juergen Hoeller - * @since 4.0 - * @deprecated as of 6.0.3, in favor of - * {@link org.springframework.web.socket.client.standard.StandardWebSocketClient} - */ -@Deprecated(since = "6.0.3", forRemoval = true) -public class JettyWebSocketClient extends AbstractWebSocketClient implements Lifecycle { - - private final org.eclipse.jetty.websocket.client.WebSocketClient client; - - @Nullable - private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(); - - - /** - * Default constructor that creates an instance of - * {@link org.eclipse.jetty.websocket.client.WebSocketClient}. - */ - public JettyWebSocketClient() { - this.client = new org.eclipse.jetty.websocket.client.WebSocketClient(); - } - - /** - * Constructor that accepts an existing - * {@link org.eclipse.jetty.websocket.client.WebSocketClient} instance. - */ - public JettyWebSocketClient(WebSocketClient client) { - this.client = client; - } - - - /** - * Set an {@link AsyncTaskExecutor} to use when opening connections. - *

If this property is set to {@code null}, calls to any of the - * {@code doHandshake} methods will block until the connection is established. - *

By default an instance of {@code SimpleAsyncTaskExecutor} is used. - */ - public void setTaskExecutor(@Nullable AsyncTaskExecutor taskExecutor) { - this.taskExecutor = taskExecutor; - } - - /** - * Return the configured {@link AsyncTaskExecutor}. - */ - @Nullable - public AsyncTaskExecutor getTaskExecutor() { - return this.taskExecutor; - } - - - @Override - public void start() { - try { - this.client.start(); - } - catch (Exception ex) { - throw new IllegalStateException("Failed to start Jetty WebSocketClient", ex); - } - } - - @Override - public void stop() { - try { - this.client.stop(); - } - catch (Exception ex) { - logger.error("Failed to stop Jetty WebSocketClient", ex); - } - } - - @Override - public boolean isRunning() { - return this.client.isStarted(); - } - - - @Override - public CompletableFuture executeInternal(WebSocketHandler wsHandler, - HttpHeaders headers, final URI uri, List protocols, - List extensions, Map attributes) { - - final ClientUpgradeRequest request = new ClientUpgradeRequest(); - request.setSubProtocols(protocols); - - for (WebSocketExtension extension : extensions) { - request.addExtensions(new WebSocketToJettyExtensionConfigAdapter(extension)); - } - - request.setHeaders(headers); - - Principal user = getUser(); - JettyWebSocketSession wsSession = new JettyWebSocketSession(attributes, user); - - Callable connectTask = () -> { - JettyWebSocketHandlerAdapter adapter = new JettyWebSocketHandlerAdapter(wsHandler, wsSession); - Future future = this.client.connect(adapter, uri, request); - future.get(this.client.getConnectTimeout() + 2000, TimeUnit.MILLISECONDS); - return wsSession; - }; - - if (this.taskExecutor != null) { - return FutureUtils.callAsync(connectTask, this.taskExecutor); - } - else { - return FutureUtils.callAsync(connectTask); - } - } - - /** - * Return the user to make available through {@link WebSocketSession#getPrincipal()}. - * By default, this method returns {@code null} - */ - @Nullable - protected Principal getUser() { - return null; - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/package-info.java deleted file mode 100644 index c970db9920..0000000000 --- a/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Client-side support for the Jetty WebSocket API. - */ -@NonNullApi -@NonNullFields -package org.springframework.web.socket.client.jetty; - -import org.springframework.lang.NonNullApi; -import org.springframework.lang.NonNullFields; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/jetty/JettyRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/jetty/JettyRequestUpgradeStrategy.java index 57387844ad..835d01d705 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/jetty/JettyRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/jetty/JettyRequestUpgradeStrategy.java @@ -25,8 +25,8 @@ import java.util.Map; import jakarta.servlet.ServletContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.websocket.server.JettyWebSocketCreator; -import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer; +import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketCreator; +import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServerContainer; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/JettyXhrTransport.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/JettyXhrTransport.java index d6a91be662..231ca8d8fc 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/JettyXhrTransport.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/JettyXhrTransport.java @@ -23,11 +23,11 @@ import java.util.Enumeration; import java.util.Iterator; import java.util.concurrent.CompletableFuture; +import org.eclipse.jetty.client.ContentResponse; import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.client.util.StringRequestContent; +import org.eclipse.jetty.client.Request; +import org.eclipse.jetty.client.Response; +import org.eclipse.jetty.client.StringRequestContent; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpMethod; @@ -186,11 +186,11 @@ public class JettyXhrTransport extends AbstractXhrTransport implements Lifecycle /** - * Jetty client {@link org.eclipse.jetty.client.api.Response.Listener Response + * Jetty client {@link org.eclipse.jetty.client.Response.Listener Response * Listener} that splits the body of the response into SockJS frames and * delegates them to the {@link XhrClientSockJsSession}. */ - private class SockJsResponseListener extends Response.Listener.Adapter { + private class SockJsResponseListener implements Response.Listener { private final URI transportUrl; diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/AbstractWebSocketIntegrationTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/AbstractWebSocketIntegrationTests.java index 0f3e8b876b..270dc4468e 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/AbstractWebSocketIntegrationTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/AbstractWebSocketIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,7 +63,6 @@ public abstract class AbstractWebSocketIntegrationTests { @SuppressWarnings("removal") static Stream argumentsFactory() { return Stream.of( - arguments(named("Jetty", new JettyWebSocketTestServer()), named("Jetty", new org.springframework.web.socket.client.jetty.JettyWebSocketClient())), arguments(named("Tomcat", new TomcatWebSocketTestServer()), named("Standard", new StandardWebSocketClient())), arguments(named("Undertow", new UndertowTestServer()), named("Standard", new StandardWebSocketClient()))); } diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/JettyWebSocketTestServer.java b/spring-websocket/src/test/java/org/springframework/web/socket/JettyWebSocketTestServer.java index 67a6771779..5202e6a4b6 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/JettyWebSocketTestServer.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/JettyWebSocketTestServer.java @@ -21,13 +21,13 @@ import java.util.EnumSet; import jakarta.servlet.DispatcherType; import jakarta.servlet.Filter; import jakarta.servlet.ServletContext; +import org.eclipse.jetty.ee10.servlet.FilterHolder; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.servlet.ServletHolder; +import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.NetworkConnector; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.FilterHolder; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketHandlerAdapterTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketHandlerAdapterTests.java index 36bcf2671b..8f7f23ab09 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketHandlerAdapterTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketHandlerAdapterTests.java @@ -51,7 +51,7 @@ class JettyWebSocketHandlerAdapterTests { @Test void onOpen() throws Exception { - this.adapter.onWebSocketConnect(this.session); + this.adapter.onWebSocketOpen(this.session); verify(this.webSocketHandler).afterConnectionEstablished(this.webSocketSession); } diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/client/jetty/JettyWebSocketClientTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/client/jetty/JettyWebSocketClientTests.java deleted file mode 100644 index 2a4faef0aa..0000000000 --- a/spring-websocket/src/test/java/org/springframework/web/socket/client/jetty/JettyWebSocketClientTests.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.web.socket.client.jetty; - -/** - * Tests for {@link JettyWebSocketClient}. - * - * @author Rossen Stoyanchev - */ -public class JettyWebSocketClientTests { - - /* TODO: complete upgrade to Jetty 11 - private JettyWebSocketClient client; - - private TestJettyWebSocketServer server; - - private String wsUrl; - - private WebSocketSession wsSession; - - - @BeforeEach - public void setup() throws Exception { - - this.server = new TestJettyWebSocketServer(new TextWebSocketHandler()); - this.server.start(); - - this.client = new JettyWebSocketClient(); - this.client.start(); - - this.wsUrl = "ws://localhost:" + this.server.getPort() + "/test"; - } - - @AfterEach - public void teardown() throws Exception { - this.wsSession.close(); - this.client.stop(); - this.server.stop(); - } - - - @Test - public void doHandshake() throws Exception { - - WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); - headers.setSecWebSocketProtocol(Arrays.asList("echo")); - - this.wsSession = this.client.doHandshake(new TextWebSocketHandler(), headers, new URI(this.wsUrl)).get(); - - assertThat(this.wsSession.getUri().toString()).isEqualTo(this.wsUrl); - assertThat(this.wsSession.getAcceptedProtocol()).isEqualTo("echo"); - } - - @Test - public void doHandshakeWithTaskExecutor() throws Exception { - - WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); - headers.setSecWebSocketProtocol(Arrays.asList("echo")); - - this.client.setTaskExecutor(new SimpleAsyncTaskExecutor()); - this.wsSession = this.client.doHandshake(new TextWebSocketHandler(), headers, new URI(this.wsUrl)).get(); - - assertThat(this.wsSession.getUri().toString()).isEqualTo(this.wsUrl); - assertThat(this.wsSession.getAcceptedProtocol()).isEqualTo("echo"); - } - - - private static class TestJettyWebSocketServer { - - private final Server server; - - - public TestJettyWebSocketServer(final WebSocketHandler webSocketHandler) { - - this.server = new Server(); - ServerConnector connector = new ServerConnector(this.server); - connector.setPort(0); - - this.server.addConnector(connector); - this.server.setHandler(new WebSocketUpgradeHandler() { - @Override - public void configure(JettyWebSocketServletFactory factory) { - factory.setCreator(new JettyWebSocketCreator() { - @Override - public Object createWebSocket(JettyServerUpgradeRequest req, JettyServerUpgradeResponse resp) { - if (!CollectionUtils.isEmpty(req.getSubProtocols())) { - resp.setAcceptedSubProtocol(req.getSubProtocols().get(0)); - } - JettyWebSocketSession session = new JettyWebSocketSession(null, null); - return new JettyWebSocketHandlerAdapter(webSocketHandler, session); - } - }); - } - }); - } - - public void start() throws Exception { - this.server.start(); - } - - public void stop() throws Exception { - this.server.stop(); - } - - public int getPort() { - return ((ServerConnector) this.server.getConnectors()[0]).getLocalPort(); - } - } - */ - -} diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/JettySockJsIntegrationTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/JettySockJsIntegrationTests.java deleted file mode 100644 index 945a58a23d..0000000000 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/JettySockJsIntegrationTests.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2002-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.web.socket.sockjs.client; - -import org.eclipse.jetty.client.HttpClient; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.socket.JettyWebSocketTestServer; -import org.springframework.web.socket.server.RequestUpgradeStrategy; -import org.springframework.web.socket.server.jetty.JettyRequestUpgradeStrategy; - -/** - * SockJS integration tests using Jetty for client and server. - * - * @author Rossen Stoyanchev - */ -class JettySockJsIntegrationTests extends AbstractSockJsIntegrationTests { - - @Override - protected Class upgradeStrategyConfigClass() { - return JettyTestConfig.class; - } - - @Override - protected JettyWebSocketTestServer createWebSocketTestServer() { - return new JettyWebSocketTestServer(); - } - - @SuppressWarnings("removal") - @Override - protected Transport createWebSocketTransport() { - return new WebSocketTransport(new org.springframework.web.socket.client.jetty.JettyWebSocketClient()); - } - - @Override - protected AbstractXhrTransport createXhrTransport() { - return new JettyXhrTransport(new HttpClient()); - } - - - @Configuration(proxyBeanMethods = false) - static class JettyTestConfig { - @Bean - RequestUpgradeStrategy upgradeStrategy() { - return new JettyRequestUpgradeStrategy(); - } - } - -}