Skip to content

Commit

Permalink
Implement Eclipse Jetty core HTTP handler adapter
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
3 people authored and simonbasle committed Jul 8, 2024
1 parent b7ec028 commit 0a60c62
Show file tree
Hide file tree
Showing 33 changed files with 1,692 additions and 517 deletions.
4 changes: 2 additions & 2 deletions framework-platform/framework-platform.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ dependencies {
api(platform("org.apache.groovy:groovy-bom:4.0.21"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.assertj:assertj-bom:3.26.0"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.10"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.10"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.11"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.11"))
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.3"))
Expand Down
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();
}
}

}
Loading

0 comments on commit 0a60c62

Please sign in to comment.