Browse Source
This provides an implementation of an HTTP Handler Adapter that is coded directly to the Eclipse Jetty core API, bypassing any servlet implementation. This includes a Jetty implementation of the spring `WebSocketClient` interface, `JettyWebSocketClient`, using an explicit dependency to the jetty-websocket-api. Closes gh-32097 Co-authored-by: Lachlan Roberts <lachlan@webtide.com> Co-authored-by: Arjen Poutsma <arjen.poutsma@broadcom.com>pull/33181/head
33 changed files with 1692 additions and 517 deletions
@ -0,0 +1,359 @@
@@ -0,0 +1,359 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.core.io.buffer; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.nio.charset.Charset; |
||||
import java.util.concurrent.atomic.AtomicInteger; |
||||
import java.util.function.IntPredicate; |
||||
|
||||
import org.eclipse.jetty.io.Content; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Implementation of the {@code DataBuffer} interface that can wrap a Jetty |
||||
* {@link Content.Chunk}. Typically constructed with {@link JettyDataBufferFactory}. |
||||
* |
||||
* @author Greg Wilkins |
||||
* @author Lachlan Roberts |
||||
* @author Arjen Poutsma |
||||
* @since 6.2 |
||||
*/ |
||||
public final class JettyDataBuffer implements PooledDataBuffer { |
||||
|
||||
private final DefaultDataBuffer delegate; |
||||
|
||||
@Nullable |
||||
private final Content.Chunk chunk; |
||||
|
||||
private final JettyDataBufferFactory bufferFactory; |
||||
|
||||
private final AtomicInteger refCount = new AtomicInteger(1); |
||||
|
||||
|
||||
JettyDataBuffer(JettyDataBufferFactory bufferFactory, DefaultDataBuffer delegate, Content.Chunk chunk) { |
||||
Assert.notNull(bufferFactory, "BufferFactory must not be null"); |
||||
Assert.notNull(delegate, "Delegate must not be null"); |
||||
Assert.notNull(chunk, "Chunk must not be null"); |
||||
|
||||
this.bufferFactory = bufferFactory; |
||||
this.delegate = delegate; |
||||
this.chunk = chunk; |
||||
this.chunk.retain(); |
||||
} |
||||
|
||||
JettyDataBuffer(JettyDataBufferFactory bufferFactory, DefaultDataBuffer delegate) { |
||||
Assert.notNull(bufferFactory, "BufferFactory must not be null"); |
||||
Assert.notNull(delegate, "Delegate must not be null"); |
||||
|
||||
this.bufferFactory = bufferFactory; |
||||
this.delegate = delegate; |
||||
this.chunk = null; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isAllocated() { |
||||
return this.refCount.get() > 0; |
||||
} |
||||
|
||||
@Override |
||||
public PooledDataBuffer retain() { |
||||
int result = this.refCount.updateAndGet(c -> { |
||||
if (c != 0) { |
||||
return c + 1; |
||||
} |
||||
else { |
||||
return 0; |
||||
} |
||||
}); |
||||
if (result != 0 && this.chunk != null) { |
||||
this.chunk.retain(); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public PooledDataBuffer touch(Object hint) { |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public boolean release() { |
||||
int result = this.refCount.updateAndGet(c -> { |
||||
if (c != 0) { |
||||
return c - 1; |
||||
} |
||||
else { |
||||
throw new IllegalStateException("JettyDataBuffer already released: " + this); |
||||
} |
||||
}); |
||||
if (this.chunk != null) { |
||||
return this.chunk.release(); |
||||
} |
||||
else { |
||||
return result == 0; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public DataBufferFactory factory() { |
||||
return this.bufferFactory; |
||||
} |
||||
|
||||
// delegation
|
||||
|
||||
@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; |
||||
} |
||||
|
||||
@Override |
||||
public byte getByte(int index) { |
||||
return this.delegate.getByte(index); |
||||
} |
||||
|
||||
@Override |
||||
public byte read() { |
||||
return this.delegate.read(); |
||||
} |
||||
|
||||
@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) { |
||||
DefaultDataBuffer delegateSlice = this.delegate.slice(index, length); |
||||
if (this.chunk != null) { |
||||
this.chunk.retain(); |
||||
return new JettyDataBuffer(this.bufferFactory, delegateSlice, this.chunk); |
||||
} |
||||
else { |
||||
return new JettyDataBuffer(this.bufferFactory, delegateSlice); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public DataBuffer split(int index) { |
||||
DefaultDataBuffer delegateSplit = this.delegate.split(index); |
||||
if (this.chunk != null) { |
||||
this.chunk.retain(); |
||||
return new JettyDataBuffer(this.bufferFactory, delegateSplit, this.chunk); |
||||
} |
||||
else { |
||||
return new JettyDataBuffer(this.bufferFactory, delegateSplit); |
||||
} |
||||
} |
||||
|
||||
@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(); |
||||
if (this.chunk != null) { |
||||
return new JettyByteBufferIterator(delegateIterator, this.chunk); |
||||
} |
||||
else { |
||||
return delegateIterator; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public ByteBufferIterator writableByteBuffers() { |
||||
ByteBufferIterator delegateIterator = this.delegate.writableByteBuffers(); |
||||
if (this.chunk != null) { |
||||
return new JettyByteBufferIterator(delegateIterator, this.chunk); |
||||
} |
||||
else { |
||||
return delegateIterator; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public String toString(int index, int length, Charset charset) { |
||||
return this.delegate.toString(index, length, charset); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return this.delegate.hashCode(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
return this == o || (o instanceof JettyDataBuffer other && |
||||
this.delegate.equals(other.delegate)); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return String.format("JettyDataBuffer (r: %d, w: %d, c: %d)", |
||||
readPosition(), writePosition(), capacity()); |
||||
} |
||||
|
||||
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(); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,108 @@
@@ -0,0 +1,108 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.core.io.buffer; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.util.List; |
||||
|
||||
import org.eclipse.jetty.io.Content; |
||||
|
||||
/** |
||||
* Implementation of the {@code DataBufferFactory} interface that creates |
||||
* {@link JettyDataBuffer} instances. |
||||
* |
||||
* @author Arjen Poutsma |
||||
* @since 6.2 |
||||
*/ |
||||
public class JettyDataBufferFactory implements DataBufferFactory { |
||||
|
||||
private final DefaultDataBufferFactory delegate; |
||||
|
||||
|
||||
/** |
||||
* Creates a new {@code JettyDataBufferFactory} with default settings. |
||||
*/ |
||||
public JettyDataBufferFactory() { |
||||
this(false); |
||||
} |
||||
|
||||
/** |
||||
* Creates a new {@code JettyDataBufferFactory}, indicating whether direct |
||||
* buffers should be created by {@link #allocateBuffer()} and |
||||
* {@link #allocateBuffer(int)}. |
||||
* @param preferDirect {@code true} if direct buffers are to be preferred; |
||||
* {@code false} otherwise |
||||
*/ |
||||
public JettyDataBufferFactory(boolean preferDirect) { |
||||
this(preferDirect, DefaultDataBufferFactory.DEFAULT_INITIAL_CAPACITY); |
||||
} |
||||
|
||||
/** |
||||
* Creates a new {@code JettyDataBufferFactory}, indicating whether direct |
||||
* buffers should be created by {@link #allocateBuffer()} and |
||||
* {@link #allocateBuffer(int)}, and what the capacity is to be used for |
||||
* {@link #allocateBuffer()}. |
||||
* @param preferDirect {@code true} if direct buffers are to be preferred; |
||||
* {@code false} otherwise |
||||
*/ |
||||
public JettyDataBufferFactory(boolean preferDirect, int defaultInitialCapacity) { |
||||
this.delegate = new DefaultDataBufferFactory(preferDirect, defaultInitialCapacity); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
@Deprecated |
||||
public JettyDataBuffer allocateBuffer() { |
||||
DefaultDataBuffer delegate = this.delegate.allocateBuffer(); |
||||
return new JettyDataBuffer(this, delegate); |
||||
} |
||||
|
||||
@Override |
||||
public JettyDataBuffer allocateBuffer(int initialCapacity) { |
||||
DefaultDataBuffer delegate = this.delegate.allocateBuffer(initialCapacity); |
||||
return new JettyDataBuffer(this, delegate); |
||||
} |
||||
|
||||
@Override |
||||
public JettyDataBuffer wrap(ByteBuffer byteBuffer) { |
||||
DefaultDataBuffer delegate = this.delegate.wrap(byteBuffer); |
||||
return new JettyDataBuffer(this, delegate); |
||||
} |
||||
|
||||
@Override |
||||
public JettyDataBuffer wrap(byte[] bytes) { |
||||
DefaultDataBuffer delegate = this.delegate.wrap(bytes); |
||||
return new JettyDataBuffer(this, delegate); |
||||
} |
||||
|
||||
public JettyDataBuffer wrap(Content.Chunk chunk) { |
||||
ByteBuffer byteBuffer = chunk.getByteBuffer(); |
||||
DefaultDataBuffer delegate = this.delegate.wrap(byteBuffer); |
||||
return new JettyDataBuffer(this, delegate, chunk); |
||||
} |
||||
|
||||
@Override |
||||
public JettyDataBuffer join(List<? extends DataBuffer> dataBuffers) { |
||||
DefaultDataBuffer delegate = this.delegate.join(dataBuffers); |
||||
return new JettyDataBuffer(this, delegate); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isDirect() { |
||||
return this.delegate.isDirect(); |
||||
} |
||||
} |
||||
@ -0,0 +1,59 @@
@@ -0,0 +1,59 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.core.io.buffer; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
|
||||
import org.eclipse.jetty.io.Content; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
import static org.mockito.BDDMockito.given; |
||||
import static org.mockito.BDDMockito.then; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.times; |
||||
|
||||
/** |
||||
* @author Arjen Poutsma |
||||
*/ |
||||
public class JettyDataBufferTests { |
||||
|
||||
private final JettyDataBufferFactory dataBufferFactory = new JettyDataBufferFactory(); |
||||
|
||||
@Test |
||||
void releaseRetainChunk() { |
||||
ByteBuffer buffer = ByteBuffer.allocate(3); |
||||
Content.Chunk mockChunk = mock(); |
||||
given(mockChunk.getByteBuffer()).willReturn(buffer); |
||||
given(mockChunk.release()).willReturn(false, false, true); |
||||
|
||||
|
||||
|
||||
JettyDataBuffer dataBuffer = this.dataBufferFactory.wrap(mockChunk); |
||||
dataBuffer.retain(); |
||||
dataBuffer.retain(); |
||||
assertThat(dataBuffer.release()).isFalse(); |
||||
assertThat(dataBuffer.release()).isFalse(); |
||||
assertThat(dataBuffer.release()).isTrue(); |
||||
|
||||
assertThatIllegalStateException().isThrownBy(dataBuffer::release); |
||||
|
||||
then(mockChunk).should(times(3)).retain(); |
||||
then(mockChunk).should(times(3)).release(); |
||||
} |
||||
} |
||||
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.http.server.reactive; |
||||
|
||||
import org.eclipse.jetty.server.Handler; |
||||
import org.eclipse.jetty.server.Request; |
||||
import org.eclipse.jetty.server.Response; |
||||
import org.eclipse.jetty.util.Callback; |
||||
|
||||
import org.springframework.core.io.buffer.JettyDataBufferFactory; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Adapt {@link HttpHandler} to the Jetty {@link org.eclipse.jetty.server.Handler} abstraction. |
||||
* |
||||
* @author Greg Wilkins |
||||
* @author Lachlan Roberts |
||||
* @author Arjen Poutsma |
||||
* @since 6.2 |
||||
*/ |
||||
public class JettyCoreHttpHandlerAdapter extends Handler.Abstract.NonBlocking { |
||||
|
||||
private final HttpHandler httpHandler; |
||||
|
||||
private JettyDataBufferFactory dataBufferFactory = new JettyDataBufferFactory(); |
||||
|
||||
|
||||
public JettyCoreHttpHandlerAdapter(HttpHandler httpHandler) { |
||||
this.httpHandler = httpHandler; |
||||
} |
||||
|
||||
public void setDataBufferFactory(JettyDataBufferFactory dataBufferFactory) { |
||||
Assert.notNull(dataBufferFactory, "DataBufferFactory must not be null"); |
||||
this.dataBufferFactory = dataBufferFactory; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception { |
||||
this.httpHandler.handle(new JettyCoreServerHttpRequest(request, this.dataBufferFactory), |
||||
new JettyCoreServerHttpResponse(response, this.dataBufferFactory)) |
||||
.subscribe(unused -> {}, callback::failed, callback::succeeded); |
||||
return true; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,120 @@
@@ -0,0 +1,120 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.http.server.reactive; |
||||
|
||||
import java.net.InetSocketAddress; |
||||
import java.net.SocketAddress; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
import org.eclipse.jetty.io.Content; |
||||
import org.eclipse.jetty.io.EndPoint; |
||||
import org.eclipse.jetty.server.Request; |
||||
import org.reactivestreams.FlowAdapters; |
||||
import reactor.core.publisher.Flux; |
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer; |
||||
import org.springframework.core.io.buffer.JettyDataBufferFactory; |
||||
import org.springframework.http.HttpCookie; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.http.support.JettyHeadersAdapter; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.CollectionUtils; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
|
||||
/** |
||||
* Adapt an Eclipse Jetty {@link Request} to a {@link org.springframework.http.server.ServerHttpRequest}. |
||||
* |
||||
* @author Greg Wilkins |
||||
* @author Arjen Poutsma |
||||
* @since 6.2 |
||||
*/ |
||||
class JettyCoreServerHttpRequest extends AbstractServerHttpRequest { |
||||
|
||||
private final JettyDataBufferFactory dataBufferFactory; |
||||
|
||||
private final Request request; |
||||
|
||||
|
||||
public JettyCoreServerHttpRequest(Request request, JettyDataBufferFactory dataBufferFactory) { |
||||
super(HttpMethod.valueOf(request.getMethod()), |
||||
request.getHttpURI().toURI(), |
||||
request.getContext().getContextPath(), |
||||
new HttpHeaders(new JettyHeadersAdapter(request.getHeaders()))); |
||||
this.dataBufferFactory = dataBufferFactory; |
||||
this.request = request; |
||||
} |
||||
|
||||
@Override |
||||
protected MultiValueMap<String, HttpCookie> initCookies() { |
||||
List<org.eclipse.jetty.http.HttpCookie> httpCookies = Request.getCookies(this.request); |
||||
if (httpCookies.isEmpty()) { |
||||
return CollectionUtils.toMultiValueMap(Collections.emptyMap()); |
||||
} |
||||
MultiValueMap<String, HttpCookie> cookies =new LinkedMultiValueMap<>(); |
||||
for (org.eclipse.jetty.http.HttpCookie c : httpCookies) { |
||||
cookies.add(c.getName(), new HttpCookie(c.getName(), c.getValue())); |
||||
} |
||||
return cookies; |
||||
} |
||||
|
||||
@Override |
||||
@Nullable |
||||
public SslInfo initSslInfo() { |
||||
if (this.request.getConnectionMetaData().isSecure() && |
||||
this.request.getAttribute(EndPoint.SslSessionData.ATTRIBUTE) instanceof EndPoint.SslSessionData sessionData) { |
||||
return new DefaultSslInfo(sessionData.sslSessionId(), sessionData.peerCertificates()); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Override |
||||
public <T> T getNativeRequest() { |
||||
return (T) this.request; |
||||
} |
||||
|
||||
@Override |
||||
protected String initId() { |
||||
return this.request.getId(); |
||||
} |
||||
|
||||
@Override |
||||
@Nullable |
||||
public InetSocketAddress getLocalAddress() { |
||||
SocketAddress localAddress = this.request.getConnectionMetaData().getLocalSocketAddress(); |
||||
return localAddress instanceof InetSocketAddress inet ? inet : null; |
||||
} |
||||
|
||||
@Override |
||||
@Nullable |
||||
public InetSocketAddress getRemoteAddress() { |
||||
SocketAddress remoteAddress = this.request.getConnectionMetaData().getRemoteSocketAddress(); |
||||
return remoteAddress instanceof InetSocketAddress inet ? inet : null; |
||||
} |
||||
|
||||
@Override |
||||
public Flux<DataBuffer> getBody() { |
||||
// We access the request body as a Flow.Publisher, which is wrapped as an org.reactivestreams.Publisher and
|
||||
// then wrapped as a Flux.
|
||||
return Flux.from(FlowAdapters.toPublisher(Content.Source.asPublisher(this.request))) |
||||
.map(this.dataBufferFactory::wrap); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,237 @@
@@ -0,0 +1,237 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.http.server.reactive; |
||||
|
||||
import java.nio.file.Path; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.ListIterator; |
||||
import java.util.Map; |
||||
|
||||
import org.eclipse.jetty.http.HttpCookie; |
||||
import org.eclipse.jetty.http.HttpField; |
||||
import org.eclipse.jetty.io.Content; |
||||
import org.eclipse.jetty.server.HttpCookieUtils; |
||||
import org.eclipse.jetty.server.Response; |
||||
import org.eclipse.jetty.util.Callback; |
||||
import org.eclipse.jetty.util.IteratingCallback; |
||||
import org.reactivestreams.Publisher; |
||||
import reactor.core.publisher.Flux; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer; |
||||
import org.springframework.core.io.buffer.DataBufferUtils; |
||||
import org.springframework.core.io.buffer.JettyDataBufferFactory; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpStatusCode; |
||||
import org.springframework.http.ResponseCookie; |
||||
import org.springframework.http.ZeroCopyHttpOutputMessage; |
||||
import org.springframework.http.support.JettyHeadersAdapter; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
/** |
||||
* Adapt an Eclipse Jetty {@link Response} to a {@link org.springframework.http.server.ServerHttpResponse}. |
||||
* |
||||
* @author Greg Wilkins |
||||
* @author Lachlan Roberts |
||||
* @since 6.2 |
||||
*/ |
||||
class JettyCoreServerHttpResponse extends AbstractServerHttpResponse implements ZeroCopyHttpOutputMessage { |
||||
|
||||
private final Response response; |
||||
|
||||
public JettyCoreServerHttpResponse(Response response, JettyDataBufferFactory dataBufferFactory) { |
||||
super(dataBufferFactory, new HttpHeaders(new JettyHeadersAdapter(response.getHeaders()))); |
||||
this.response = response; |
||||
|
||||
// remove all existing cookies from the response and add them to the cookie map, to be added back later
|
||||
for (ListIterator<HttpField> i = this.response.getHeaders().listIterator(); i.hasNext(); ) { |
||||
HttpField f = i.next(); |
||||
if (f instanceof HttpCookieUtils.SetCookieHttpField setCookieHttpField) { |
||||
HttpCookie httpCookie = setCookieHttpField.getHttpCookie(); |
||||
ResponseCookie responseCookie = ResponseCookie.from(httpCookie.getName(), httpCookie.getValue()) |
||||
.httpOnly(httpCookie.isHttpOnly()) |
||||
.domain(httpCookie.getDomain()) |
||||
.maxAge(httpCookie.getMaxAge()) |
||||
.sameSite(httpCookie.getSameSite().name()) |
||||
.secure(httpCookie.isSecure()) |
||||
.partitioned(httpCookie.isPartitioned()) |
||||
.build(); |
||||
this.addCookie(responseCookie); |
||||
i.remove(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected Mono<Void> writeWithInternal(Publisher<? extends DataBuffer> body) { |
||||
return Flux.from(body) |
||||
.concatMap(this::sendDataBuffer) |
||||
.then(); |
||||
} |
||||
|
||||
@Override |
||||
protected Mono<Void> writeAndFlushWithInternal(Publisher<? extends Publisher<? extends DataBuffer>> body) { |
||||
return Flux.from(body).concatMap(this::writeWithInternal).then(); |
||||
} |
||||
|
||||
@Override |
||||
protected void applyStatusCode() { |
||||
HttpStatusCode status = getStatusCode(); |
||||
this.response.setStatus(status == null ? 0 : status.value()); |
||||
} |
||||
|
||||
@Override |
||||
protected void applyHeaders() { |
||||
} |
||||
|
||||
@Override |
||||
protected void applyCookies() { |
||||
this.getCookies().values().stream() |
||||
.flatMap(List::stream) |
||||
.forEach(cookie -> Response.addCookie(this.response, new ResponseHttpCookie(cookie))); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<Void> writeWith(Path file, long position, long count) { |
||||
Callback.Completable callback = new Callback.Completable(); |
||||
Mono<Void> mono = Mono.fromFuture(callback); |
||||
try { |
||||
Content.copy(Content.Source.from(null, file, position, count), this.response, callback); |
||||
} |
||||
catch (Throwable th) { |
||||
callback.failed(th); |
||||
} |
||||
return doCommit(() -> mono); |
||||
} |
||||
|
||||
private Mono<Void> sendDataBuffer(DataBuffer dataBuffer) { |
||||
return Mono.defer(() -> { |
||||
DataBuffer.ByteBufferIterator byteBufferIterator = dataBuffer.readableByteBuffers(); |
||||
Callback.Completable callback = new Callback.Completable(); |
||||
new IteratingCallback() { |
||||
@Override |
||||
protected Action process() { |
||||
if (!byteBufferIterator.hasNext()) { |
||||
return Action.SUCCEEDED; |
||||
} |
||||
response.write(false, byteBufferIterator.next(), this); |
||||
return Action.SCHEDULED; |
||||
} |
||||
|
||||
@Override |
||||
protected void onCompleteSuccess() { |
||||
byteBufferIterator.close(); |
||||
DataBufferUtils.release(dataBuffer); |
||||
callback.complete(null); |
||||
} |
||||
|
||||
@Override |
||||
protected void onCompleteFailure(Throwable cause) { |
||||
byteBufferIterator.close(); |
||||
DataBufferUtils.release(dataBuffer); |
||||
callback.failed(cause); |
||||
} |
||||
}.iterate(); |
||||
|
||||
return Mono.fromFuture(callback); |
||||
}); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Override |
||||
public <T> T getNativeResponse() { |
||||
return (T) this.response; |
||||
} |
||||
|
||||
private static class ResponseHttpCookie implements org.eclipse.jetty.http.HttpCookie { |
||||
private final ResponseCookie responseCookie; |
||||
|
||||
public ResponseHttpCookie(ResponseCookie responseCookie) { |
||||
this.responseCookie = responseCookie; |
||||
} |
||||
|
||||
public ResponseCookie getResponseCookie() { |
||||
return this.responseCookie; |
||||
} |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return this.responseCookie.getName(); |
||||
} |
||||
|
||||
@Override |
||||
public String getValue() { |
||||
return this.responseCookie.getValue(); |
||||
} |
||||
|
||||
@Override |
||||
public int getVersion() { |
||||
return 0; |
||||
} |
||||
|
||||
@Override |
||||
public long getMaxAge() { |
||||
return this.responseCookie.getMaxAge().toSeconds(); |
||||
} |
||||
|
||||
@Override |
||||
@Nullable |
||||
public String getComment() { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
@Nullable |
||||
public String getDomain() { |
||||
return this.responseCookie.getDomain(); |
||||
} |
||||
|
||||
@Override |
||||
@Nullable |
||||
public String getPath() { |
||||
return this.responseCookie.getPath(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isSecure() { |
||||
return this.responseCookie.isSecure(); |
||||
} |
||||
|
||||
@Nullable |
||||
@Override |
||||
public SameSite getSameSite() { |
||||
// Adding non-null return site breaks tests.
|
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isHttpOnly() { |
||||
return this.responseCookie.isHttpOnly(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isPartitioned() { |
||||
return this.responseCookie.isPartitioned(); |
||||
} |
||||
|
||||
@Override |
||||
public Map<String, String> getAttributes() { |
||||
return Collections.emptyMap(); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,98 @@
@@ -0,0 +1,98 @@
|
||||
/* |
||||
* 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. |
||||
* 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.testfixture.http.server.reactive.bootstrap; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.eclipse.jetty.io.ArrayByteBufferPool; |
||||
import org.eclipse.jetty.server.Server; |
||||
import org.eclipse.jetty.server.ServerConnector; |
||||
import org.eclipse.jetty.websocket.server.ServerWebSocketContainer; |
||||
|
||||
import org.springframework.http.server.reactive.JettyCoreHttpHandlerAdapter; |
||||
|
||||
/** |
||||
* @author Rossen Stoyanchev |
||||
* @author Sam Brannen |
||||
* @author Greg Wilkins |
||||
* @since 6.2 |
||||
*/ |
||||
public class JettyCoreHttpServer extends AbstractHttpServer { |
||||
|
||||
protected Log logger = LogFactory.getLog(getClass().getName()); |
||||
|
||||
private ArrayByteBufferPool byteBufferPool; |
||||
|
||||
private Server jettyServer; |
||||
|
||||
@Override |
||||
protected void initServer() { |
||||
if (logger.isTraceEnabled()) |
||||
this.byteBufferPool = new ArrayByteBufferPool.Tracking(); |
||||
this.jettyServer = new Server(null, null, byteBufferPool); |
||||
|
||||
ServerConnector connector = new ServerConnector(this.jettyServer); |
||||
connector.setHost(getHost()); |
||||
connector.setPort(getPort()); |
||||
this.jettyServer.addConnector(connector); |
||||
this.jettyServer.setHandler(createHandlerAdapter()); |
||||
|
||||
ServerWebSocketContainer.ensure(jettyServer); |
||||
} |
||||
|
||||
private JettyCoreHttpHandlerAdapter createHandlerAdapter() { |
||||
return new JettyCoreHttpHandlerAdapter(resolveHttpHandler()); |
||||
} |
||||
|
||||
@Override |
||||
protected void startInternal() throws Exception { |
||||
this.jettyServer.start(); |
||||
setPort(((ServerConnector) this.jettyServer.getConnectors()[0]).getLocalPort()); |
||||
} |
||||
|
||||
@Override |
||||
protected void stopInternal() { |
||||
boolean wasRunning = this.jettyServer.isRunning(); |
||||
try { |
||||
this.jettyServer.stop(); |
||||
} |
||||
catch (Exception ex) { |
||||
// ignore
|
||||
} |
||||
|
||||
// TODO remove this or make debug only
|
||||
if (wasRunning && this.byteBufferPool instanceof ArrayByteBufferPool.Tracking tracking) { |
||||
if (!tracking.getLeaks().isEmpty()) { |
||||
System.err.println("Leaks:\n" + tracking.dumpLeaks()); |
||||
throw new IllegalStateException("LEAKS"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void resetInternal() { |
||||
try { |
||||
if (this.jettyServer.isRunning()) { |
||||
stopInternal(); |
||||
} |
||||
this.jettyServer.destroy(); |
||||
} |
||||
finally { |
||||
this.jettyServer = null; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,111 @@
@@ -0,0 +1,111 @@
|
||||
/* |
||||
* 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.Objects; |
||||
import java.util.concurrent.atomic.AtomicReference; |
||||
|
||||
import org.eclipse.jetty.client.Request; |
||||
import org.eclipse.jetty.client.Response; |
||||
import org.eclipse.jetty.http.HttpHeader; |
||||
import org.eclipse.jetty.util.component.LifeCycle; |
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; |
||||
import org.eclipse.jetty.websocket.client.JettyUpgradeListener; |
||||
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.lang.Nullable; |
||||
import org.springframework.web.reactive.socket.HandshakeInfo; |
||||
import org.springframework.web.reactive.socket.WebSocketHandler; |
||||
import org.springframework.web.reactive.socket.adapter.JettyWebSocketHandlerAdapter; |
||||
import org.springframework.web.reactive.socket.adapter.JettyWebSocketSession; |
||||
|
||||
public class JettyWebSocketClient implements WebSocketClient, Lifecycle { |
||||
|
||||
private final org.eclipse.jetty.websocket.client.WebSocketClient client; |
||||
|
||||
public JettyWebSocketClient() { |
||||
this(new org.eclipse.jetty.websocket.client.WebSocketClient()); |
||||
} |
||||
|
||||
public JettyWebSocketClient(org.eclipse.jetty.websocket.client.WebSocketClient client) { |
||||
this.client = client; |
||||
} |
||||
|
||||
@Override |
||||
public void start() { |
||||
LifeCycle.start(this.client); |
||||
} |
||||
|
||||
@Override |
||||
public void stop() { |
||||
LifeCycle.stop(this.client); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isRunning() { |
||||
return this.client.isRunning(); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<Void> execute(URI url, WebSocketHandler handler) { |
||||
return execute(url, null, handler); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<Void> execute(URI url, @Nullable HttpHeaders headers, WebSocketHandler handler) { |
||||
|
||||
ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); |
||||
upgradeRequest.setSubProtocols(handler.getSubProtocols()); |
||||
if (headers != null) { |
||||
headers.keySet().forEach(header -> upgradeRequest.setHeader(header, headers.getValuesAsList(header))); |
||||
} |
||||
|
||||
final AtomicReference<HandshakeInfo> handshakeInfo = new AtomicReference<>(); |
||||
JettyUpgradeListener jettyUpgradeListener = new JettyUpgradeListener() { |
||||
@Override |
||||
public void onHandshakeResponse(Request request, Response response) { |
||||
String protocol = response.getHeaders().get(HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL); |
||||
HttpHeaders responseHeaders = new HttpHeaders(); |
||||
response.getHeaders().forEach(header -> responseHeaders.add(header.getName(), header.getValue())); |
||||
handshakeInfo.set(new HandshakeInfo(url, responseHeaders, Mono.empty(), protocol)); |
||||
} |
||||
}; |
||||
|
||||
Sinks.Empty<Void> completion = Sinks.empty(); |
||||
JettyWebSocketHandlerAdapter handlerAdapter = new JettyWebSocketHandlerAdapter(handler, session -> |
||||
new JettyWebSocketSession(session, Objects.requireNonNull(handshakeInfo.get()), DefaultDataBufferFactory.sharedInstance, completion)); |
||||
try { |
||||
this.client.connect(handlerAdapter, url, upgradeRequest, jettyUpgradeListener) |
||||
.exceptionally(throwable -> { |
||||
// Only fail the completion if we have an error
|
||||
// as the JettyWebSocketSession will never be opened.
|
||||
completion.tryEmitError(throwable); |
||||
return null; |
||||
}); |
||||
return completion.asMono(); |
||||
} |
||||
catch (IOException ex) { |
||||
return Mono.error(ex); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,127 @@
@@ -0,0 +1,127 @@
|
||||
/* |
||||
* 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. |
||||
* 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.server.upgrade; |
||||
|
||||
import java.util.function.Consumer; |
||||
import java.util.function.Supplier; |
||||
|
||||
import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServerContainer; |
||||
import org.eclipse.jetty.server.Request; |
||||
import org.eclipse.jetty.server.Response; |
||||
import org.eclipse.jetty.server.Server; |
||||
import org.eclipse.jetty.util.Callback; |
||||
import org.eclipse.jetty.websocket.api.Configurable; |
||||
import org.eclipse.jetty.websocket.api.exceptions.WebSocketException; |
||||
import org.eclipse.jetty.websocket.server.ServerWebSocketContainer; |
||||
import org.eclipse.jetty.websocket.server.WebSocketCreator; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.core.io.buffer.DataBufferFactory; |
||||
import org.springframework.http.server.reactive.ServerHttpRequest; |
||||
import org.springframework.http.server.reactive.ServerHttpRequestDecorator; |
||||
import org.springframework.http.server.reactive.ServerHttpResponse; |
||||
import org.springframework.http.server.reactive.ServerHttpResponseDecorator; |
||||
import org.springframework.lang.Nullable; |
||||
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; |
||||
import org.springframework.web.reactive.socket.server.RequestUpgradeStrategy; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
|
||||
/** |
||||
* A WebSocket {@code RequestUpgradeStrategy} for Jetty 12 Core. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 5.3.4 |
||||
*/ |
||||
public class JettyCoreRequestUpgradeStrategy implements RequestUpgradeStrategy { |
||||
|
||||
@Nullable |
||||
private Consumer<Configurable> webSocketConfigurer; |
||||
|
||||
@Nullable |
||||
private ServerWebSocketContainer serverContainer; |
||||
|
||||
/** |
||||
* Add a callback to configure WebSocket server parameters on |
||||
* {@link JettyWebSocketServerContainer}. |
||||
* @since 6.1 |
||||
*/ |
||||
public void addWebSocketConfigurer(Consumer<Configurable> webSocketConfigurer) { |
||||
this.webSocketConfigurer = (this.webSocketConfigurer != null ? |
||||
this.webSocketConfigurer.andThen(webSocketConfigurer) : webSocketConfigurer); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<Void> upgrade( |
||||
ServerWebExchange exchange, WebSocketHandler handler, |
||||
@Nullable String subProtocol, Supplier<HandshakeInfo> handshakeInfoFactory) { |
||||
|
||||
ServerHttpRequest request = exchange.getRequest(); |
||||
ServerHttpResponse response = exchange.getResponse(); |
||||
|
||||
Request jettyRequest = ServerHttpRequestDecorator.getNativeRequest(request); |
||||
Response jettyResponse = ServerHttpResponseDecorator.getNativeResponse(response); |
||||
|
||||
HandshakeInfo handshakeInfo = handshakeInfoFactory.get(); |
||||
DataBufferFactory factory = response.bufferFactory(); |
||||
|
||||
// Trigger WebFlux preCommit actions before upgrade
|
||||
return exchange.getResponse().setComplete() |
||||
.then(Mono.deferContextual(contextView -> { |
||||
JettyWebSocketHandlerAdapter adapter = new JettyWebSocketHandlerAdapter( |
||||
ContextWebSocketHandler.decorate(handler, contextView), |
||||
session -> new JettyWebSocketSession(session, handshakeInfo, factory)); |
||||
|
||||
WebSocketCreator webSocketCreator = (upgradeRequest, upgradeResponse, callback) -> { |
||||
if (subProtocol != null) { |
||||
upgradeResponse.setAcceptedSubProtocol(subProtocol); |
||||
} |
||||
return adapter; |
||||
}; |
||||
|
||||
Callback.Completable callback = new Callback.Completable(); |
||||
Mono<Void> mono = Mono.fromFuture(callback); |
||||
ServerWebSocketContainer container = getWebSocketServerContainer(jettyRequest); |
||||
try { |
||||
if (!container.upgrade(webSocketCreator, jettyRequest, jettyResponse, callback)) { |
||||
throw new WebSocketException("request could not be upgraded to websocket"); |
||||
} |
||||
} |
||||
catch (WebSocketException ex) { |
||||
callback.failed(ex); |
||||
} |
||||
|
||||
return mono; |
||||
})); |
||||
} |
||||
|
||||
private ServerWebSocketContainer getWebSocketServerContainer(Request jettyRequest) { |
||||
if (this.serverContainer == null) { |
||||
Server server = jettyRequest.getConnectionMetaData().getConnector().getServer(); |
||||
ServerWebSocketContainer container = ServerWebSocketContainer.get(server.getContext()); |
||||
if (this.webSocketConfigurer != null) { |
||||
this.webSocketConfigurer.accept(container); |
||||
} |
||||
this.serverContainer = container; |
||||
} |
||||
return this.serverContainer; |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue