diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/Jetty10HttpFieldsHelper.java b/spring-web/src/main/java/org/springframework/http/client/reactive/Jetty10HttpFieldsHelper.java new file mode 100644 index 0000000000..83ff92994d --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/Jetty10HttpFieldsHelper.java @@ -0,0 +1,93 @@ +/* + * Copyright 2002-2023 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.http.client.reactive; + +import java.lang.reflect.Method; + +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.api.Response; + +import org.springframework.http.HttpHeaders; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + +/** + * Used to support Jetty 10. + * + * @author Rossen Stoyanchev + * @author Arjen Poutsma + * @since 5.3.26 + */ +abstract class Jetty10HttpFieldsHelper { + + private static final boolean jetty10Present; + + private static final Method requestGetHeadersMethod; + + private static final Method responseGetHeadersMethod; + + private static final Method getNameMethod; + + private static final Method getValueMethod; + + + static { + try { + ClassLoader classLoader = JettyClientHttpResponse.class.getClassLoader(); + Class httpFieldsClass = classLoader.loadClass("org.eclipse.jetty.http.HttpFields"); + jetty10Present = httpFieldsClass.isInterface(); + requestGetHeadersMethod = Request.class.getMethod("getHeaders"); + responseGetHeadersMethod = Response.class.getMethod("getHeaders"); + Class httpFieldClass = classLoader.loadClass("org.eclipse.jetty.http.HttpField"); + getNameMethod = httpFieldClass.getMethod("getName"); + getValueMethod = httpFieldClass.getMethod("getValue"); + } + catch (ClassNotFoundException | NoSuchMethodException ex) { + throw new IllegalStateException("No compatible Jetty version found", ex); + } + } + + + public static boolean jetty10Present() { + return jetty10Present; + } + + public static HttpHeaders getHttpHeaders(Request request) { + Iterable iterator = (Iterable) + ReflectionUtils.invokeMethod(requestGetHeadersMethod, request); + return getHttpHeadersInternal(iterator); + } + + public static HttpHeaders getHttpHeaders(Response response) { + Iterable iterator = (Iterable) + ReflectionUtils.invokeMethod(responseGetHeadersMethod, response); + return getHttpHeadersInternal(iterator); + } + + private static HttpHeaders getHttpHeadersInternal(@Nullable Iterable iterator) { + Assert.notNull(iterator, "Iterator must not be null"); + HttpHeaders headers = new HttpHeaders(); + for (Object field : iterator) { + String name = (String) ReflectionUtils.invokeMethod(getNameMethod, field); + Assert.notNull(name, "Header name must not be null"); + String value = (String) ReflectionUtils.invokeMethod(getValueMethod, field); + headers.add(name, value); + } + return headers; + } +} diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpRequest.java index 4e1df906eb..f9412f4f78 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -37,6 +37,7 @@ import org.springframework.core.io.buffer.PooledDataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.util.MultiValueMap; /** * {@link ClientHttpRequest} implementation for the Jetty ReactiveStreams HTTP client. @@ -54,7 +55,6 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest { private final ReactiveRequest.Builder builder; - public JettyClientHttpRequest(Request jettyRequest, DataBufferFactory bufferFactory) { this.jettyRequest = jettyRequest; this.bufferFactory = bufferFactory; @@ -144,7 +144,11 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest { @Override protected HttpHeaders initReadOnlyHeaders() { - return HttpHeaders.readOnlyHttpHeaders(new JettyHeadersAdapter(this.jettyRequest.getHeaders())); + MultiValueMap headers = (Jetty10HttpFieldsHelper.jetty10Present() ? + Jetty10HttpFieldsHelper.getHttpHeaders(this.jettyRequest) : + new JettyHeadersAdapter(this.jettyRequest.getHeaders())); + + return HttpHeaders.readOnlyHttpHeaders(headers); } public ReactiveRequest toReactiveRequest() { diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpResponse.java index 5d88c83ddf..eed9e42f2f 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 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,13 +16,11 @@ 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; @@ -32,11 +30,9 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseCookie; import org.springframework.lang.Nullable; -import org.springframework.util.Assert; 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. @@ -50,10 +46,6 @@ class JettyClientHttpResponse implements ClientHttpResponse { private static final Pattern SAMESITE_PATTERN = Pattern.compile("(?i).*SameSite=(Strict|Lax|None).*"); - private static final ClassLoader classLoader = JettyClientHttpResponse.class.getClassLoader(); - - private static final boolean jetty10Present; - private final ReactiveResponse reactiveResponse; @@ -62,23 +54,12 @@ class JettyClientHttpResponse implements ClientHttpResponse { private final HttpHeaders headers; - static { - try { - Class httpFieldsClass = classLoader.loadClass("org.eclipse.jetty.http.HttpFields"); - jetty10Present = httpFieldsClass.isInterface(); - } - catch (ClassNotFoundException ex) { - throw new IllegalStateException("No compatible Jetty version found", ex); - } - } - - public JettyClientHttpResponse(ReactiveResponse reactiveResponse, Publisher content) { this.reactiveResponse = reactiveResponse; this.content = Flux.from(content); - MultiValueMap headers = (jetty10Present ? - Jetty10HttpFieldsHelper.getHttpHeaders(reactiveResponse) : + MultiValueMap headers = (Jetty10HttpFieldsHelper.jetty10Present() ? + Jetty10HttpFieldsHelper.getHttpHeaders(reactiveResponse.getResponse()) : new JettyHeadersAdapter(reactiveResponse.getHeaders())); this.headers = HttpHeaders.readOnlyHttpHeaders(headers); @@ -132,40 +113,4 @@ 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 = classLoader.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()); - Assert.notNull(iterator, "Iterator must not be null"); - for (Object field : iterator) { - String name = (String) ReflectionUtils.invokeMethod(getNameMethod, field); - Assert.notNull(name, "Header name must not be null"); - String value = (String) ReflectionUtils.invokeMethod(getValueMethod, field); - headers.add(name, value); - } - return headers; - } - } - }