Skip to content

Commit 3b65506

Browse files
committed
Use ByteBuffer support in ServletHttpHandlerAdapter
As of Servlet 6.1, the `ServletInputStream` and `ServletOutputStream` offer read and write variants based on `ByteBuffer` instead of byte arrays. This can improve performance and avoid memory copy for I/O calls. This was already partially supported for some servers like Tomcat through specific adapters. This commit moves this support to the standard `ServletHttpHandlerAdapter` and makes it available for all Servlet 6.1+ containers. Closes gh-33748
1 parent f5ff84a commit 3b65506

File tree

5 files changed

+40
-175
lines changed

5 files changed

+40
-175
lines changed

spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java

-83
This file was deleted.

spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java

+28-14
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.net.InetSocketAddress;
2121
import java.net.URI;
2222
import java.net.URISyntaxException;
23+
import java.nio.ByteBuffer;
2324
import java.nio.charset.Charset;
2425
import java.security.cert.X509Certificate;
2526
import java.util.Enumeration;
@@ -38,6 +39,7 @@
3839

3940
import org.springframework.core.io.buffer.DataBuffer;
4041
import org.springframework.core.io.buffer.DataBufferFactory;
42+
import org.springframework.core.io.buffer.DataBufferUtils;
4143
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
4244
import org.springframework.http.HttpCookie;
4345
import org.springframework.http.HttpHeaders;
@@ -58,6 +60,7 @@
5860
*
5961
* @author Rossen Stoyanchev
6062
* @author Juergen Hoeller
63+
* @author Brian Clozel
6164
* @since 5.0
6265
*/
6366
class ServletServerHttpRequest extends AbstractServerHttpRequest {
@@ -75,7 +78,7 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
7578

7679
private final DataBufferFactory bufferFactory;
7780

78-
private final byte[] buffer;
81+
private final int bufferSize;
7982

8083
private final AsyncListener asyncListener;
8184

@@ -99,7 +102,7 @@ public ServletServerHttpRequest(MultiValueMap<String, String> headers, HttpServl
99102

100103
this.request = request;
101104
this.bufferFactory = bufferFactory;
102-
this.buffer = new byte[bufferSize];
105+
this.bufferSize = bufferSize;
103106

104107
this.asyncListener = new RequestAsyncListener();
105108

@@ -275,20 +278,31 @@ protected final ServletInputStream getInputStream() {
275278
* or {@link #EOF_BUFFER} if the input stream returned -1.
276279
*/
277280
DataBuffer readFromInputStream() throws IOException {
278-
int read = this.inputStream.read(this.buffer);
279-
logBytesRead(read);
280-
281-
if (read > 0) {
282-
DataBuffer dataBuffer = this.bufferFactory.allocateBuffer(read);
283-
dataBuffer.write(this.buffer, 0, read);
284-
return dataBuffer;
281+
DataBuffer dataBuffer = this.bufferFactory.allocateBuffer(this.bufferSize);
282+
int read = -1;
283+
try {
284+
try (DataBuffer.ByteBufferIterator iterator = dataBuffer.writableByteBuffers()) {
285+
Assert.state(iterator.hasNext(), "No ByteBuffer available");
286+
ByteBuffer byteBuffer = iterator.next();
287+
read = this.inputStream.read(byteBuffer);
288+
}
289+
logBytesRead(read);
290+
if (read > 0) {
291+
dataBuffer.writePosition(read);
292+
return dataBuffer;
293+
}
294+
else if (read == -1) {
295+
return EOF_BUFFER;
296+
}
297+
else {
298+
return AbstractListenerReadPublisher.EMPTY_BUFFER;
299+
}
285300
}
286-
287-
if (read == -1) {
288-
return EOF_BUFFER;
301+
finally {
302+
if (read <= 0) {
303+
DataBufferUtils.release(dataBuffer);
304+
}
289305
}
290-
291-
return AbstractListenerReadPublisher.EMPTY_BUFFER;
292306
}
293307

294308
protected final void logBytesRead(int read) {

spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java

+10-9
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
package org.springframework.http.server.reactive;
1818

1919
import java.io.IOException;
20-
import java.io.InputStream;
20+
import java.nio.ByteBuffer;
2121
import java.nio.charset.Charset;
2222

2323
import jakarta.servlet.AsyncContext;
@@ -45,6 +45,7 @@
4545
*
4646
* @author Rossen Stoyanchev
4747
* @author Juergen Hoeller
48+
* @author Brian Clozel
4849
* @since 5.0
4950
*/
5051
class ServletServerHttpResponse extends AbstractListenerServerHttpResponse {
@@ -222,15 +223,15 @@ protected final ServletOutputStream getOutputStream() {
222223
*/
223224
protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException {
224225
ServletOutputStream outputStream = this.outputStream;
225-
InputStream input = dataBuffer.asInputStream();
226-
int bytesWritten = 0;
227-
byte[] buffer = new byte[this.bufferSize];
228-
int bytesRead;
229-
while (outputStream.isReady() && (bytesRead = input.read(buffer)) != -1) {
230-
outputStream.write(buffer, 0, bytesRead);
231-
bytesWritten += bytesRead;
226+
int len = 0;
227+
try (DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers()) {
228+
while (iterator.hasNext() && outputStream.isReady()) {
229+
ByteBuffer byteBuffer = iterator.next();
230+
len += byteBuffer.remaining();
231+
outputStream.write(byteBuffer);
232+
}
232233
}
233-
return bytesWritten;
234+
return len;
234235
}
235236

236237
private void flush() throws IOException {

spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java

+1-63
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,16 +26,12 @@
2626
import jakarta.servlet.http.HttpServletRequestWrapper;
2727
import jakarta.servlet.http.HttpServletResponse;
2828
import jakarta.servlet.http.HttpServletResponseWrapper;
29-
import org.apache.catalina.connector.CoyoteInputStream;
30-
import org.apache.catalina.connector.CoyoteOutputStream;
3129
import org.apache.catalina.connector.RequestFacade;
3230
import org.apache.catalina.connector.ResponseFacade;
3331
import org.apache.coyote.Request;
3432
import org.apache.coyote.Response;
3533

36-
import org.springframework.core.io.buffer.DataBuffer;
3734
import org.springframework.core.io.buffer.DataBufferFactory;
38-
import org.springframework.core.io.buffer.DataBufferUtils;
3935
import org.springframework.http.HttpHeaders;
4036
import org.springframework.util.Assert;
4137
import org.springframework.util.MultiValueMap;
@@ -81,10 +77,6 @@ private static final class TomcatServerHttpRequest extends ServletServerHttpRequ
8177

8278
private static final Field COYOTE_REQUEST_FIELD;
8379

84-
private final int bufferSize;
85-
86-
private final DataBufferFactory factory;
87-
8880
static {
8981
Field field = ReflectionUtils.findField(RequestFacade.class, "request");
9082
Assert.state(field != null, "Incompatible Tomcat implementation");
@@ -97,8 +89,6 @@ private static final class TomcatServerHttpRequest extends ServletServerHttpRequ
9789
throws IOException, URISyntaxException {
9890

9991
super(createTomcatHttpHeaders(request), request, context, servletPath, factory, bufferSize);
100-
this.factory = factory;
101-
this.bufferSize = bufferSize;
10292
}
10393

10494
private static MultiValueMap<String, String> createTomcatHttpHeaders(HttpServletRequest request) {
@@ -124,41 +114,6 @@ else if (request instanceof HttpServletRequestWrapper wrapper) {
124114
}
125115
}
126116

127-
@Override
128-
protected DataBuffer readFromInputStream() throws IOException {
129-
if (getInputStream() instanceof CoyoteInputStream coyoteInputStream) {
130-
DataBuffer dataBuffer = this.factory.allocateBuffer(this.bufferSize);
131-
int read = -1;
132-
try {
133-
try (DataBuffer.ByteBufferIterator iterator = dataBuffer.writableByteBuffers()) {
134-
Assert.state(iterator.hasNext(), "No ByteBuffer available");
135-
ByteBuffer byteBuffer = iterator.next();
136-
read = coyoteInputStream.read(byteBuffer);
137-
}
138-
logBytesRead(read);
139-
if (read > 0) {
140-
dataBuffer.writePosition(read);
141-
return dataBuffer;
142-
}
143-
else if (read == -1) {
144-
return EOF_BUFFER;
145-
}
146-
else {
147-
return AbstractListenerReadPublisher.EMPTY_BUFFER;
148-
}
149-
}
150-
finally {
151-
if (read <= 0) {
152-
DataBufferUtils.release(dataBuffer);
153-
}
154-
}
155-
}
156-
else {
157-
// It's possible InputStream can be wrapped, preventing use of CoyoteInputStream
158-
return super.readFromInputStream();
159-
}
160-
161-
}
162117
}
163118

164119

@@ -208,23 +163,6 @@ protected void applyHeaders() {
208163
adaptHeaders(true);
209164
}
210165

211-
@Override
212-
protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException {
213-
if (getOutputStream() instanceof CoyoteOutputStream coyoteOutputStream) {
214-
int len = 0;
215-
try (DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers()) {
216-
while (iterator.hasNext() && coyoteOutputStream.isReady()) {
217-
ByteBuffer byteBuffer = iterator.next();
218-
len += byteBuffer.remaining();
219-
coyoteOutputStream.write(byteBuffer);
220-
}
221-
}
222-
return len;
223-
}
224-
else {
225-
return super.writeToOutputStream(dataBuffer);
226-
}
227-
}
228166
}
229167

230168
}

spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/JettyHttpServer.java

+1-6
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import org.eclipse.jetty.server.Server;
2323
import org.eclipse.jetty.server.ServerConnector;
2424

25-
import org.springframework.http.server.reactive.JettyHttpHandlerAdapter;
2625
import org.springframework.http.server.reactive.ServletHttpHandlerAdapter;
2726

2827
/**
@@ -41,7 +40,7 @@ protected void initServer() throws Exception {
4140

4241
this.jettyServer = new Server();
4342

44-
ServletHttpHandlerAdapter servlet = createServletAdapter();
43+
ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(resolveHttpHandler());
4544
ServletHolder servletHolder = new ServletHolder(servlet);
4645
servletHolder.setAsyncSupported(true);
4746

@@ -56,10 +55,6 @@ protected void initServer() throws Exception {
5655
this.jettyServer.setHandler(this.contextHandler);
5756
}
5857

59-
private ServletHttpHandlerAdapter createServletAdapter() {
60-
return new JettyHttpHandlerAdapter(resolveHttpHandler());
61-
}
62-
6358
@Override
6459
protected void startInternal() throws Exception {
6560
this.jettyServer.start();

0 commit comments

Comments
 (0)