Early support for Jetty 12 (developed against 12.0.0.alpha2)
Reflective getHeaders calls to be revisited; see GitHub issue #8938 in Jetty project. HttpOutput optimization commented out still in order to avoid alpha build dependency. See gh-29575
This commit is contained in:
parent
4ff18741fd
commit
45d45c2989
|
@ -37,6 +37,12 @@ dependencies {
|
|||
optional("org.eclipse.jetty:jetty-servlet") {
|
||||
exclude group: "jakarta.servlet", module: "jakarta.servlet-api"
|
||||
}
|
||||
/* Jetty 12: see org.springframework.http.server.reactive.JettyHttpHandlerAdapter
|
||||
optional("org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.0.alpha2") {
|
||||
exclude group: "jakarta.servlet", module: "jakarta.servlet-api"
|
||||
exclude group: "org.eclipse.jetty", module: "jetty-session"
|
||||
}
|
||||
*/
|
||||
optional("org.eclipse.jetty:jetty-reactive-httpclient")
|
||||
optional('org.apache.httpcomponents.client5:httpclient5')
|
||||
optional('org.apache.httpcomponents.core5:httpcore5-reactive')
|
||||
|
|
|
@ -18,7 +18,6 @@ package org.springframework.http.client.reactive;
|
|||
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -186,7 +185,6 @@ class JettyHeadersAdapter implements MultiValueMap<String, String> {
|
|||
public Iterator<Entry<String, List<String>>> iterator() {
|
||||
return new EntryIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return headers.size();
|
||||
|
@ -203,16 +201,16 @@ class JettyHeadersAdapter implements MultiValueMap<String, String> {
|
|||
|
||||
private class EntryIterator implements Iterator<Entry<String, List<String>>> {
|
||||
|
||||
private final Enumeration<String> names = headers.getFieldNames();
|
||||
private final Iterator<String> names = headers.getFieldNamesCollection().iterator();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.names.hasMoreElements();
|
||||
return this.names.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<String, List<String>> next() {
|
||||
return new HeaderEntry(this.names.nextElement());
|
||||
return new HeaderEntry(this.names.next());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ package org.springframework.http.server.reactive;
|
|||
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -46,8 +45,8 @@ class JettyHeadersAdapter implements MultiValueMap<String, String> {
|
|||
private final HttpFields.Mutable headers;
|
||||
|
||||
|
||||
JettyHeadersAdapter(HttpFields.Mutable headers) {
|
||||
this.headers = headers;
|
||||
JettyHeadersAdapter(HttpFields headers) {
|
||||
this.headers = (headers instanceof HttpFields.Mutable mutable ? mutable : HttpFields.build(headers));
|
||||
}
|
||||
|
||||
|
||||
|
@ -170,7 +169,6 @@ class JettyHeadersAdapter implements MultiValueMap<String, String> {
|
|||
public Iterator<Entry<String, List<String>>> iterator() {
|
||||
return new EntryIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return headers.size();
|
||||
|
@ -187,16 +185,16 @@ class JettyHeadersAdapter implements MultiValueMap<String, String> {
|
|||
|
||||
private class EntryIterator implements Iterator<Entry<String, List<String>>> {
|
||||
|
||||
private final Enumeration<String> names = headers.getFieldNames();
|
||||
private final Iterator<String> names = headers.getFieldNamesCollection().iterator();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.names.hasMoreElements();
|
||||
return this.names.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<String, List<String>> next() {
|
||||
return new HeaderEntry(this.names.nextElement());
|
||||
return new HeaderEntry(this.names.next());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,12 +17,13 @@
|
|||
package org.springframework.http.server.reactive;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import jakarta.servlet.AsyncContext;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
@ -36,8 +37,11 @@ import org.springframework.core.io.buffer.DataBuffer;
|
|||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link ServletHttpHandlerAdapter} extension that uses Jetty APIs for writing
|
||||
|
@ -50,6 +54,23 @@ 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());
|
||||
|
||||
/* Jetty 12: see spring-web.gradle
|
||||
private static final boolean jetty12Present = ClassUtils.isPresent(
|
||||
"org.eclipse.jetty.ee10.servlet.HttpOutput", JettyHttpHandlerAdapter.class.getClassLoader());
|
||||
*/
|
||||
|
||||
@Nullable
|
||||
private static final Method getRequestHeadersMethod =
|
||||
ClassUtils.getMethodIfAvailable(Request.class, "getHeaders");
|
||||
|
||||
@Nullable
|
||||
private static final Method getResponseHeadersMethod =
|
||||
ClassUtils.getMethodIfAvailable(Response.class, "getHeaders");
|
||||
|
||||
|
||||
public JettyHttpHandlerAdapter(HttpHandler httpHandler) {
|
||||
super(httpHandler);
|
||||
}
|
||||
|
@ -84,7 +105,13 @@ public class JettyHttpHandlerAdapter extends ServletHttpHandlerAdapter {
|
|||
|
||||
private static MultiValueMap<String, String> createHeaders(HttpServletRequest servletRequest) {
|
||||
Request request = getRequest(servletRequest);
|
||||
HttpFields.Mutable fields = HttpFields.build(request.getHttpFields());
|
||||
HttpFields fields;
|
||||
if (getRequestHeadersMethod != null) {
|
||||
fields = (HttpFields) ReflectionUtils.invokeMethod(getRequestHeadersMethod, request);
|
||||
}
|
||||
else {
|
||||
fields = request.getHttpFields();
|
||||
}
|
||||
return new JettyHeadersAdapter(fields);
|
||||
}
|
||||
|
||||
|
@ -115,7 +142,13 @@ public class JettyHttpHandlerAdapter extends ServletHttpHandlerAdapter {
|
|||
|
||||
private static HttpHeaders createHeaders(HttpServletResponse servletResponse) {
|
||||
Response response = getResponse(servletResponse);
|
||||
HttpFields.Mutable fields = response.getHttpFields();
|
||||
HttpFields fields;
|
||||
if (getResponseHeadersMethod != null) {
|
||||
fields = (HttpFields) ReflectionUtils.invokeMethod(getResponseHeadersMethod, response);
|
||||
}
|
||||
else {
|
||||
fields = response.getHttpFields();
|
||||
}
|
||||
return new HttpHeaders(new JettyHeadersAdapter(fields));
|
||||
}
|
||||
|
||||
|
@ -135,16 +168,32 @@ public class JettyHttpHandlerAdapter extends ServletHttpHandlerAdapter {
|
|||
|
||||
@Override
|
||||
protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException {
|
||||
ByteBuffer input = dataBuffer.toByteBuffer();
|
||||
int len = input.remaining();
|
||||
ServletResponse response = getNativeResponse();
|
||||
((HttpOutput) response.getOutputStream()).write(input);
|
||||
return len;
|
||||
OutputStream output = getOutputStream();
|
||||
if (jetty11Present) {
|
||||
if (output instanceof HttpOutput httpOutput) {
|
||||
ByteBuffer input = dataBuffer.toByteBuffer();
|
||||
int len = input.remaining();
|
||||
httpOutput.write(input);
|
||||
return len;
|
||||
}
|
||||
}
|
||||
/* Jetty 12: see spring-web.gradle
|
||||
else if (jetty12Present) {
|
||||
if (output instanceof org.eclipse.jetty.ee10.servlet.HttpOutput httpOutput) {
|
||||
ByteBuffer input = dataBuffer.toByteBuffer();
|
||||
int len = input.remaining();
|
||||
httpOutput.write(input);
|
||||
return len;
|
||||
}
|
||||
}
|
||||
*/
|
||||
return super.writeToOutputStream(dataBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyHeaders() {
|
||||
HttpServletResponse response = getNativeResponse();
|
||||
|
||||
MediaType contentType = null;
|
||||
try {
|
||||
contentType = getHeaders().getContentType();
|
||||
|
@ -156,10 +205,12 @@ public class JettyHttpHandlerAdapter extends ServletHttpHandlerAdapter {
|
|||
if (response.getContentType() == null && contentType != null) {
|
||||
response.setContentType(contentType.toString());
|
||||
}
|
||||
|
||||
Charset charset = (contentType != null ? contentType.getCharset() : null);
|
||||
if (response.getCharacterEncoding() == null && charset != null) {
|
||||
response.setCharacterEncoding(charset.name());
|
||||
}
|
||||
|
||||
long contentLength = getHeaders().getContentLength();
|
||||
if (contentLength != -1) {
|
||||
response.setContentLengthLong(contentLength);
|
||||
|
|
|
@ -56,6 +56,7 @@ import org.springframework.util.StringUtils;
|
|||
* Adapt {@link ServerHttpRequest} to the Servlet {@link HttpServletRequest}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @since 5.0
|
||||
*/
|
||||
class ServletServerHttpRequest extends AbstractServerHttpRequest {
|
||||
|
@ -65,6 +66,8 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
|
|||
|
||||
private final HttpServletRequest request;
|
||||
|
||||
private final ServletInputStream inputStream;
|
||||
|
||||
private final RequestBodyPublisher bodyPublisher;
|
||||
|
||||
private final Object cookieLock = new Object();
|
||||
|
@ -99,8 +102,8 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
|
|||
this.asyncListener = new RequestAsyncListener();
|
||||
|
||||
// Tomcat expects ReadListener registration on initial thread
|
||||
ServletInputStream inputStream = request.getInputStream();
|
||||
this.bodyPublisher = new RequestBodyPublisher(inputStream);
|
||||
this.inputStream = request.getInputStream();
|
||||
this.bodyPublisher = new RequestBodyPublisher(this.inputStream);
|
||||
this.bodyPublisher.registerReadListener();
|
||||
}
|
||||
|
||||
|
@ -231,6 +234,13 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
|
|||
return this.asyncListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link ServletInputStream} for the current response.
|
||||
*/
|
||||
protected final ServletInputStream getInputStream() {
|
||||
return this.inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read from the request body InputStream and return a DataBuffer.
|
||||
* Invoked only when {@link ServletInputStream#isReady()} returns "true".
|
||||
|
@ -239,7 +249,7 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
|
|||
* or {@link #EOF_BUFFER} if the input stream returned -1.
|
||||
*/
|
||||
DataBuffer readFromInputStream() throws IOException {
|
||||
int read = this.request.getInputStream().read(this.buffer);
|
||||
int read = this.inputStream.read(this.buffer);
|
||||
logBytesRead(read);
|
||||
|
||||
if (read > 0) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* 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.
|
||||
|
@ -44,6 +44,7 @@ import org.springframework.util.Assert;
|
|||
* Adapt {@link ServerHttpResponse} to the Servlet {@link HttpServletResponse}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @since 5.0
|
||||
*/
|
||||
class ServletServerHttpResponse extends AbstractListenerServerHttpResponse {
|
||||
|
@ -185,6 +186,13 @@ class ServletServerHttpResponse extends AbstractListenerServerHttpResponse {
|
|||
return processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link ServletOutputStream} for the current response.
|
||||
*/
|
||||
protected final ServletOutputStream getOutputStream() {
|
||||
return this.outputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the DataBuffer to the response body OutputStream.
|
||||
* Invoked only when {@link ServletOutputStream#isReady()} returns "true"
|
||||
|
|
|
@ -23,9 +23,6 @@ import java.nio.ByteBuffer;
|
|||
import java.nio.charset.Charset;
|
||||
|
||||
import jakarta.servlet.AsyncContext;
|
||||
import jakarta.servlet.ServletInputStream;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
@ -57,7 +54,6 @@ import org.springframework.util.ReflectionUtils;
|
|||
*/
|
||||
public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter {
|
||||
|
||||
|
||||
public TomcatHttpHandlerAdapter(HttpHandler httpHandler) {
|
||||
super(httpHandler);
|
||||
}
|
||||
|
@ -130,11 +126,11 @@ public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter {
|
|||
|
||||
@Override
|
||||
protected DataBuffer readFromInputStream() throws IOException {
|
||||
ServletInputStream inputStream = ((ServletRequest) getNativeRequest()).getInputStream();
|
||||
if (!(inputStream instanceof CoyoteInputStream coyoteInputStream)) {
|
||||
if (!(getInputStream() instanceof CoyoteInputStream coyoteInputStream)) {
|
||||
// It's possible InputStream can be wrapped, preventing use of CoyoteInputStream
|
||||
return super.readFromInputStream();
|
||||
}
|
||||
|
||||
ByteBuffer byteBuffer = this.factory.isDirect() ?
|
||||
ByteBuffer.allocateDirect(this.bufferSize) :
|
||||
ByteBuffer.allocate(this.bufferSize);
|
||||
|
@ -198,6 +194,7 @@ public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter {
|
|||
@Override
|
||||
protected void applyHeaders() {
|
||||
HttpServletResponse response = getNativeResponse();
|
||||
|
||||
MediaType contentType = null;
|
||||
try {
|
||||
contentType = getHeaders().getContentType();
|
||||
|
@ -210,10 +207,12 @@ public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter {
|
|||
response.setContentType(contentType.toString());
|
||||
}
|
||||
getHeaders().remove(HttpHeaders.CONTENT_TYPE);
|
||||
|
||||
Charset charset = (contentType != null ? contentType.getCharset() : null);
|
||||
if (response.getCharacterEncoding() == null && charset != null) {
|
||||
response.setCharacterEncoding(charset.name());
|
||||
}
|
||||
|
||||
long contentLength = getHeaders().getContentLength();
|
||||
if (contentLength != -1) {
|
||||
response.setContentLengthLong(contentLength);
|
||||
|
@ -223,10 +222,13 @@ public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter {
|
|||
|
||||
@Override
|
||||
protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException {
|
||||
if (!(getOutputStream() instanceof CoyoteOutputStream coyoteOutputStream)) {
|
||||
return super.writeToOutputStream(dataBuffer);
|
||||
}
|
||||
|
||||
ByteBuffer input = dataBuffer.toByteBuffer();
|
||||
int len = input.remaining();
|
||||
ServletResponse response = getNativeResponse();
|
||||
((CoyoteOutputStream) response.getOutputStream()).write(input);
|
||||
coyoteOutputStream.write(input);
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.io.ByteArrayOutputStream;
|
|||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
|
@ -171,9 +172,9 @@ public class JettyXhrTransport extends AbstractXhrTransport implements Lifecycle
|
|||
|
||||
private static HttpHeaders toHttpHeaders(HttpFields httpFields) {
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
Enumeration<String> names = httpFields.getFieldNames();
|
||||
while (names.hasMoreElements()) {
|
||||
String name = names.nextElement();
|
||||
Iterator<String> names = httpFields.getFieldNamesCollection().iterator();
|
||||
while (names.hasNext()) {
|
||||
String name = names.next();
|
||||
Enumeration<String> values = httpFields.getValues(name);
|
||||
while (values.hasMoreElements()) {
|
||||
String value = values.nextElement();
|
||||
|
|
Loading…
Reference in New Issue