Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Eclipse Jetty core HTTP handler adapter #32097

Open
wants to merge 153 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
153 commits
Select commit Hold shift + click to select a range
6ff974d
Initial boiler plate implementation of JettyCore HttpHandler Adaptor
gregw Jan 15, 2024
db609a7
More implementation of the request side
gregw Jan 15, 2024
7a23fbf
Created JettyCoreHttpServer
gregw Jan 15, 2024
418d699
Created JettyCoreHttpServer
gregw Jan 15, 2024
930d420
WIP on tests
gregw Jan 16, 2024
eb38434
WIP on tests
gregw Jan 16, 2024
88e7467
Cleanup start/stop for jetty
gregw Jan 16, 2024
c5a327c
retainable bytebuffer!
gregw Jan 16, 2024
49abe72
Non blocking invocation type
gregw Jan 16, 2024
6ca453c
split out into separate files
gregw Jan 16, 2024
ea71c61
minor httpHeader optimizations
gregw Jan 18, 2024
dd72a14
since updated with expected release
gregw Jan 20, 2024
50e78d3
Initial implementation of the writeWith methods in JettyCoreServerHtt…
lachlan-roberts Jan 22, 2024
ab844d6
Implement the ZeroCopyHttpOutputMessage interface for JettyCoreServer…
lachlan-roberts Jan 22, 2024
4cf2b0c
Add temporary fix for cookies
lachlan-roberts Jan 22, 2024
77772cb
disable setting of the same site cookie attribute to fix test
lachlan-roberts Jan 22, 2024
8617ded
rearrange methods in JettyCoreServerHttpResponse
lachlan-roberts Jan 22, 2024
375e3b6
fix issues with cookies, committing response, and with ZeroCopyHttpOu…
lachlan-roberts Jan 23, 2024
0db46f2
cleanups
lachlan-roberts Jan 23, 2024
2081ae0
cleanups
lachlan-roberts Jan 23, 2024
855e268
removed TODO
gregw Jan 24, 2024
191853d
Reformatted code
gregw Jan 24, 2024
c8a7cc3
Fixed unset response status bug
gregw Jan 24, 2024
85bdb2b
Request header mutation support
gregw Jan 24, 2024
39f9ce8
Do not use canonical path
gregw Jan 24, 2024
c46382e
Track leaks (for now) and retain DataBuffer chunks
gregw Jan 25, 2024
72921c0
Fixes for Jetty Core WebSocket
lachlan-roberts Jan 30, 2024
05df5d1
Temporary fix for cookies in JettyCoreServerHttpResponse with TODOs
lachlan-roberts Jan 30, 2024
89dbcbf
Do not actually commit response in JettyCoreServerHttpResponse & fix …
lachlan-roberts Jan 30, 2024
654407e
Add JettyCore upgrade option in HandshakeWebSocketService
lachlan-roberts Jan 30, 2024
802419e
move getNativeRequest/Response to ServerHttpRequest/Response interfaces
lachlan-roberts Jan 30, 2024
4518138
Reformatted code
gregw Jan 31, 2024
67622d1
WIP on fixing leaks
gregw Jan 31, 2024
0530dd7
WIP on fixing leaks
gregw Jan 31, 2024
a5b7d21
WIP on fixing tests
gregw Jan 31, 2024
f25837c
Updated to jetty 12.0.6
gregw Feb 1, 2024
7cf3214
Move all response cookies to multiMap for possible mutation by spring
gregw Feb 1, 2024
c8c3734
Use an atomicReference to release the last used databuffer after onNe…
gregw Feb 1, 2024
74aa6cb
less allocations for releasing after onNext
gregw Feb 1, 2024
befdde4
release DataBuffer from the JettyCoreServerHttpResponse
lachlan-roberts Feb 1, 2024
ee59264
fixed style
gregw Feb 3, 2024
bbd2573
fix build with jetty 12.0.7-SNAPSHOT
olamy Feb 6, 2024
b1c778d
Updated to jetty 12.0.6
gregw Feb 1, 2024
5f1712d
Implement a JettyWebSocketSession based on demand.
lachlan-roberts Feb 6, 2024
700f8e6
Add a Jetty implementation of the spring WebSocketClient interface.
lachlan-roberts Feb 6, 2024
1c7935e
Prevent possible ReadPendingException from multiple demand.
lachlan-roberts Feb 6, 2024
bbd4e68
Update the JettyWebSocketHandlerAdapter as a Session.Listener
lachlan-roberts Feb 6, 2024
1f09a94
fixes for checkstyle
lachlan-roberts Feb 6, 2024
08e9177
Ensure DataBuffer is processed correctly for BINARY messages.
lachlan-roberts Feb 12, 2024
26cac28
Wait for close of Session before calling handlerCompletionSink
lachlan-roberts Feb 13, 2024
e2b452a
fix checkstyle error
lachlan-roberts Feb 13, 2024
b356adf
Merge remote-tracking branch 'webtide/main' into JettyCoreHttpHandler…
gregw Feb 20, 2024
a0f4fe2
Changes from review.
lachlan-roberts Feb 27, 2024
0f88d7f
Merge remote-tracking branch 'webtide/main' into JettyCoreHttpHandler…
gregw Mar 7, 2024
65cf738
updated to jetty 12.0.7
gregw Mar 7, 2024
6e25189
updated to jetty 12.0.7
gregw Mar 7, 2024
8f9d07c
Merge remote-tracking branch 'webtide/main' into JettyCoreHttpHandler…
gregw Mar 7, 2024
34429fe
updated to jetty 12.0.7
gregw Mar 7, 2024
5d5e46a
fixed checkstyle
gregw Mar 22, 2024
6187cdf
Merge branch 'main' into JettyCoreHttpHandlerAdapter
gregw Mar 22, 2024
d9202dc
Merge branch 'spring-projects:main' into JettyCoreHttpHandlerAdapter
gregw Apr 4, 2024
85505fe
Implement LifeCycle for JettyWebSocketClient
lachlan-roberts Apr 9, 2024
cd9063b
additional changes from review
lachlan-roberts Apr 9, 2024
e9cb9a9
Merge pull request #2 from webtide/JettyCoreHttpHandlerAdapter-WebSocket
lachlan-roberts Apr 16, 2024
d6986ee
Use abstract request/response classes
gregw May 29, 2024
19215d4
Suppress NullAway warnings for Jetty WebSocket classes
lachlan-roberts May 29, 2024
364183c
fix other checkstyle warnings
lachlan-roberts May 29, 2024
a76465a
backed out abstract response change
gregw May 29, 2024
c0cdb2b
Merge remote-tracking branch 'origin/main' into JettyCoreHttpHandlerA…
gregw May 29, 2024
82a18ee
Steps towards abstract response class
gregw May 29, 2024
fca5d22
add explicit dependency on jetty-websocket-api
lachlan-roberts May 30, 2024
17eb8df
checkstyle
gregw May 30, 2024
38e4913
updated response to use the abstract
gregw May 30, 2024
85b4041
updated response to use the abstract
gregw May 31, 2024
df6ad58
Merge remote-tracking branch 'refs/remotes/webtide/JettyCoreHttpHandl…
gregw May 31, 2024
37daa27
Initial boiler plate implementation of JettyCore HttpHandler Adaptor
gregw Jan 15, 2024
c5b37ba
More implementation of the request side
gregw Jan 15, 2024
0ea2180
Created JettyCoreHttpServer
gregw Jan 15, 2024
49363c1
Created JettyCoreHttpServer
gregw Jan 15, 2024
090c8c4
WIP on tests
gregw Jan 16, 2024
390f52e
WIP on tests
gregw Jan 16, 2024
b884f65
Cleanup start/stop for jetty
gregw Jan 16, 2024
2aedef1
retainable bytebuffer!
gregw Jan 16, 2024
4613e8c
Non blocking invocation type
gregw Jan 16, 2024
5378423
split out into separate files
gregw Jan 16, 2024
f070f90
minor httpHeader optimizations
gregw Jan 18, 2024
dc02b28
since updated with expected release
gregw Jan 20, 2024
543c30a
Initial implementation of the writeWith methods in JettyCoreServerHtt…
lachlan-roberts Jan 22, 2024
0ef95af
Implement the ZeroCopyHttpOutputMessage interface for JettyCoreServer…
lachlan-roberts Jan 22, 2024
4c0d782
Add temporary fix for cookies
lachlan-roberts Jan 22, 2024
c5caff8
disable setting of the same site cookie attribute to fix test
lachlan-roberts Jan 22, 2024
8bb9f63
rearrange methods in JettyCoreServerHttpResponse
lachlan-roberts Jan 22, 2024
6311b33
fix issues with cookies, committing response, and with ZeroCopyHttpOu…
lachlan-roberts Jan 23, 2024
01bde19
cleanups
lachlan-roberts Jan 23, 2024
1c6d8db
cleanups
lachlan-roberts Jan 23, 2024
071cd55
removed TODO
gregw Jan 24, 2024
d18b34b
Reformatted code
gregw Jan 24, 2024
6b36d70
Fixed unset response status bug
gregw Jan 24, 2024
d86e795
Request header mutation support
gregw Jan 24, 2024
8d64408
Do not use canonical path
gregw Jan 24, 2024
6c4f7bc
Track leaks (for now) and retain DataBuffer chunks
gregw Jan 25, 2024
56e933c
Fixes for Jetty Core WebSocket
lachlan-roberts Jan 30, 2024
1bce7b9
Temporary fix for cookies in JettyCoreServerHttpResponse with TODOs
lachlan-roberts Jan 30, 2024
f934d31
Do not actually commit response in JettyCoreServerHttpResponse & fix …
lachlan-roberts Jan 30, 2024
d2e152b
Add JettyCore upgrade option in HandshakeWebSocketService
lachlan-roberts Jan 30, 2024
014c77c
move getNativeRequest/Response to ServerHttpRequest/Response interfaces
lachlan-roberts Jan 30, 2024
cd8bf6a
Reformatted code
gregw Jan 31, 2024
c20b865
WIP on fixing leaks
gregw Jan 31, 2024
a8c9e95
WIP on fixing leaks
gregw Jan 31, 2024
c6e322e
WIP on fixing tests
gregw Jan 31, 2024
e830a02
Updated to jetty 12.0.6
gregw Feb 1, 2024
14d9a76
Move all response cookies to multiMap for possible mutation by spring
gregw Feb 1, 2024
d5ff365
Use an atomicReference to release the last used databuffer after onNe…
gregw Feb 1, 2024
8efbc37
less allocations for releasing after onNext
gregw Feb 1, 2024
9a68ff7
release DataBuffer from the JettyCoreServerHttpResponse
lachlan-roberts Feb 1, 2024
011d856
fixed style
gregw Feb 3, 2024
5e71a1c
fix build with jetty 12.0.7-SNAPSHOT
olamy Feb 6, 2024
7cb17ce
updated to jetty 12.0.7
gregw Mar 7, 2024
f47baaf
updated to jetty 12.0.7
gregw Mar 7, 2024
9bba36b
fixed checkstyle
gregw Mar 22, 2024
f06131b
Implement a JettyWebSocketSession based on demand.
lachlan-roberts Feb 6, 2024
8cc8b11
Add a Jetty implementation of the spring WebSocketClient interface.
lachlan-roberts Feb 6, 2024
2b9510d
Prevent possible ReadPendingException from multiple demand.
lachlan-roberts Feb 6, 2024
96a8e99
Update the JettyWebSocketHandlerAdapter as a Session.Listener
lachlan-roberts Feb 6, 2024
f195e4e
fixes for checkstyle
lachlan-roberts Feb 6, 2024
7939314
Ensure DataBuffer is processed correctly for BINARY messages.
lachlan-roberts Feb 12, 2024
30a7c41
Wait for close of Session before calling handlerCompletionSink
lachlan-roberts Feb 13, 2024
7ccb2e1
fix checkstyle error
lachlan-roberts Feb 13, 2024
38ef5bb
Changes from review.
lachlan-roberts Feb 27, 2024
ea1dd82
Implement LifeCycle for JettyWebSocketClient
lachlan-roberts Apr 9, 2024
e32983b
additional changes from review
lachlan-roberts Apr 9, 2024
077b60f
Use abstract request/response classes
gregw May 29, 2024
fa5f800
Suppress NullAway warnings for Jetty WebSocket classes
lachlan-roberts May 29, 2024
442f11c
fix other checkstyle warnings
lachlan-roberts May 29, 2024
bd25bd7
backed out abstract response change
gregw May 29, 2024
3785321
Steps towards abstract response class
gregw May 29, 2024
d8eb3fa
checkstyle
gregw May 30, 2024
c6b1914
updated response to use the abstract
gregw May 30, 2024
a2420fc
updated response to use the abstract
gregw May 31, 2024
5fbf83c
add explicit dependency on jetty-websocket-api
lachlan-roberts May 30, 2024
9b7a2f4
Polishing
poutsma May 29, 2024
b8d208d
Replace flatMap(...,1) with concatMap
poutsma May 31, 2024
db82c22
Make copy of headers map
poutsma Jun 5, 2024
16af65a
Introduce JettyDataBuffer
poutsma Jun 5, 2024
6c48a83
Polish JettyDataBuffer
poutsma Jun 5, 2024
feef4a7
Polishing
poutsma Jun 6, 2024
d00ed1e
Polishing
poutsma Jun 6, 2024
1f2f0af
Merge remote-tracking branch 'poutsma/gh-32097' into JettyCoreHttpHan…
gregw Jun 12, 2024
08ce47f
Merge remote-tracking branch 'poutsma/gh-32097' into JettyCoreHttpHan…
gregw Jun 12, 2024
8753907
Merge remote-tracking branch 'origin/main' into JettyCoreHttpHandlerA…
gregw Jun 12, 2024
260da30
addExact
gregw Jun 12, 2024
d415757
doCommit not required
gregw Jun 12, 2024
0b4b65f
PR #32097 - changes to address reviews for websocket
lachlan-roberts Jun 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions spring-core/spring-core.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ dependencies {
optional("io.smallrye.reactive:mutiny")
optional("net.sf.jopt-simple:jopt-simple")
optional("org.aspectj:aspectjweaver")
optional("org.eclipse.jetty:jetty-io")
optional("org.jetbrains.kotlin:kotlin-reflect")
optional("org.jetbrains.kotlin:kotlin-stdlib")
optional("org.jetbrains.kotlinx:kotlinx-coroutines-core")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ public DefaultDataBuffer slice(int index, int length) {
}

@Override
public DataBuffer split(int index) {
public DefaultDataBuffer split(int index) {
checkIndex(index);

ByteBuffer split = this.byteBuffer.duplicate().clear()
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}

}