Support for Jetty 10

Closes gh-26123
This commit is contained in:
Rossen Stoyanchev
2021-01-21 20:16:21 +00:00
parent e537844a09
commit aa7584d252
11 changed files with 1006 additions and 72 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -16,11 +16,13 @@
package org.springframework.http.client.reactive;
import java.lang.reflect.Method;
import java.net.HttpCookie;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.reactive.client.ReactiveResponse;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
@@ -30,9 +32,11 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
/**
* {@link ClientHttpResponse} implementation for the Jetty ReactiveStreams HTTP client.
@@ -46,6 +50,11 @@ class JettyClientHttpResponse implements ClientHttpResponse {
private static final Pattern SAMESITE_PATTERN = Pattern.compile("(?i).*SameSite=(Strict|Lax|None).*");
private static final ClassLoader loader = JettyClientHttpResponse.class.getClassLoader();
private static final boolean jetty10Present = ClassUtils.isPresent(
"org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer", loader);
private final ReactiveResponse reactiveResponse;
@@ -58,8 +67,11 @@ class JettyClientHttpResponse implements ClientHttpResponse {
this.reactiveResponse = reactiveResponse;
this.content = Flux.from(content);
MultiValueMap<String, String> adapter = new JettyHeadersAdapter(reactiveResponse.getHeaders());
this.headers = HttpHeaders.readOnlyHttpHeaders(adapter);
MultiValueMap<String, String> headers = (jetty10Present ?
Jetty10HttpFieldsHelper.getHttpHeaders(reactiveResponse) :
new JettyHeadersAdapter(reactiveResponse.getHeaders()));
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
}
@@ -110,4 +122,38 @@ class JettyClientHttpResponse implements ClientHttpResponse {
return this.headers;
}
private static class Jetty10HttpFieldsHelper {
private static final Method getHeadersMethod;
private static final Method getNameMethod;
private static final Method getValueMethod;
static {
try {
getHeadersMethod = Response.class.getMethod("getHeaders");
Class<?> type = loader.loadClass("org.eclipse.jetty.http.HttpField");
getNameMethod = type.getMethod("getName");
getValueMethod = type.getMethod("getValue");
}
catch (ClassNotFoundException | NoSuchMethodException ex) {
throw new IllegalStateException("No compatible Jetty version found", ex);
}
}
public static HttpHeaders getHttpHeaders(ReactiveResponse response) {
HttpHeaders headers = new HttpHeaders();
Iterable<?> iterator = (Iterable<?>)
ReflectionUtils.invokeMethod(getHeadersMethod, response.getResponse());
for (Object field : iterator) {
headers.add(
(String) ReflectionUtils.invokeMethod(getNameMethod, field),
(String) ReflectionUtils.invokeMethod(getValueMethod, field));
}
return headers;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -36,6 +36,7 @@ import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MultiValueMap;
/**
@@ -49,6 +50,10 @@ import org.springframework.util.MultiValueMap;
*/
public class JettyHttpHandlerAdapter extends ServletHttpHandlerAdapter {
private static final boolean jetty10Present = ClassUtils.isPresent(
"org.eclipse.jetty.http.CookieCutter", JettyHttpHandlerAdapter.class.getClassLoader());
public JettyHttpHandlerAdapter(HttpHandler httpHandler) {
super(httpHandler);
}
@@ -58,16 +63,29 @@ public class JettyHttpHandlerAdapter extends ServletHttpHandlerAdapter {
protected ServletServerHttpRequest createRequest(HttpServletRequest request, AsyncContext context)
throws IOException, URISyntaxException {
// TODO: need to compile against Jetty 10 to use HttpFields (class->interface)
if (jetty10Present) {
return super.createRequest(request, context);
}
Assert.notNull(getServletPath(), "Servlet path is not initialized");
return new JettyServerHttpRequest(request, context, getServletPath(), getDataBufferFactory(), getBufferSize());
return new JettyServerHttpRequest(
request, context, getServletPath(), getDataBufferFactory(), getBufferSize());
}
@Override
protected ServletServerHttpResponse createResponse(HttpServletResponse response,
AsyncContext context, ServletServerHttpRequest request) throws IOException {
return new JettyServerHttpResponse(
response, context, getDataBufferFactory(), getBufferSize(), request);
// TODO: need to compile against Jetty 10 to use HttpFields (class->interface)
if (jetty10Present) {
return new BaseJettyServerHttpResponse(
response, context, getDataBufferFactory(), getBufferSize(), request);
}
else {
return new JettyServerHttpResponse(
response, context, getDataBufferFactory(), getBufferSize(), request);
}
}
@@ -87,7 +105,34 @@ public class JettyHttpHandlerAdapter extends ServletHttpHandlerAdapter {
}
private static final class JettyServerHttpResponse extends ServletServerHttpResponse {
private static class BaseJettyServerHttpResponse extends ServletServerHttpResponse {
BaseJettyServerHttpResponse(HttpServletResponse response, AsyncContext asyncContext,
DataBufferFactory bufferFactory, int bufferSize, ServletServerHttpRequest request)
throws IOException {
super(response, asyncContext, bufferFactory, bufferSize, request);
}
BaseJettyServerHttpResponse(HttpHeaders headers, HttpServletResponse response, AsyncContext asyncContext,
DataBufferFactory bufferFactory, int bufferSize, ServletServerHttpRequest request)
throws IOException {
super(headers, response, asyncContext, bufferFactory, bufferSize, request);
}
@Override
protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException {
ByteBuffer input = dataBuffer.asByteBuffer();
int len = input.remaining();
ServletResponse response = getNativeResponse();
((HttpOutput) response.getOutputStream()).write(input);
return len;
}
}
private static final class JettyServerHttpResponse extends BaseJettyServerHttpResponse {
JettyServerHttpResponse(HttpServletResponse response, AsyncContext asyncContext,
DataBufferFactory bufferFactory, int bufferSize, ServletServerHttpRequest request)
@@ -124,15 +169,6 @@ public class JettyHttpHandlerAdapter extends ServletHttpHandlerAdapter {
response.setContentLengthLong(contentLength);
}
}
@Override
protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException {
ByteBuffer input = dataBuffer.asByteBuffer();
int len = input.remaining();
ServletResponse response = getNativeResponse();
((HttpOutput) response.getOutputStream()).write(input);
return len;
}
}
}