Polishing in HttpServiceMethod

This commit is contained in:
rstoyanchev 2025-01-15 19:03:00 +00:00
parent 5150a9a6ad
commit 384d2749c6
4 changed files with 43 additions and 60 deletions

View File

@ -605,9 +605,4 @@ For method parameters and returns values, generally, `@HttpExchange` supports a
subset of the method parameters that `@RequestMapping` does. Notably, it excludes any subset of the method parameters that `@RequestMapping` does. Notably, it excludes any
server-side specific parameter types. For details, see the list for server-side specific parameter types. For details, see the list for
xref:integration/rest-clients.adoc#rest-http-interface-method-parameters[@HttpExchange] and xref:integration/rest-clients.adoc#rest-http-interface-method-parameters[@HttpExchange] and
xref:web/webflux/controller/ann-methods/arguments.adoc[@RequestMapping]. xref:web/webflux/controller/ann-methods/arguments.adoc[@RequestMapping].
`@HttpExchange` also supports a `headers()` parameter which accepts `"name=value"`-like
pairs like in `@RequestMapping(headers={})` on the client side. On the server side,
this extends to the full syntax that
xref:#webflux-ann-requestmapping-params-and-headers[`@RequestMapping`] supports.

View File

@ -22,7 +22,6 @@ import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -160,7 +159,7 @@ final class HttpServiceMethod {
private record HttpRequestValuesInitializer( private record HttpRequestValuesInitializer(
@Nullable HttpMethod httpMethod, @Nullable String url, @Nullable HttpMethod httpMethod, @Nullable String url,
@Nullable MediaType contentType, @Nullable List<MediaType> acceptMediaTypes, @Nullable MediaType contentType, @Nullable List<MediaType> acceptMediaTypes,
@Nullable MultiValueMap<String, String> otherHeaders, MultiValueMap<String, String> headers,
Supplier<HttpRequestValues.Builder> requestValuesSupplier) { Supplier<HttpRequestValues.Builder> requestValuesSupplier) {
public HttpRequestValues.Builder initializeRequestValuesBuilder() { public HttpRequestValues.Builder initializeRequestValuesBuilder() {
@ -177,16 +176,8 @@ final class HttpServiceMethod {
if (this.acceptMediaTypes != null) { if (this.acceptMediaTypes != null) {
requestValues.setAccept(this.acceptMediaTypes); requestValues.setAccept(this.acceptMediaTypes);
} }
if (this.otherHeaders != null) { this.headers.forEach((name, values) ->
this.otherHeaders.forEach((name, values) -> { values.forEach(value -> requestValues.addHeader(name, value)));
if (values.size() == 1) {
requestValues.addHeader(name, values.get(0));
}
else {
requestValues.addHeader(name, values.toArray(new String[0]));
}
});
}
return requestValues; return requestValues;
} }
@ -217,10 +208,10 @@ final class HttpServiceMethod {
String url = initUrl(typeAnnotation, methodAnnotation, embeddedValueResolver); String url = initUrl(typeAnnotation, methodAnnotation, embeddedValueResolver);
MediaType contentType = initContentType(typeAnnotation, methodAnnotation); MediaType contentType = initContentType(typeAnnotation, methodAnnotation);
List<MediaType> acceptableMediaTypes = initAccept(typeAnnotation, methodAnnotation); List<MediaType> acceptableMediaTypes = initAccept(typeAnnotation, methodAnnotation);
MultiValueMap<String, String> headers = initHeaders(typeAnnotation, methodAnnotation, MultiValueMap<String, String> headers = initHeaders(typeAnnotation, methodAnnotation, embeddedValueResolver);
embeddedValueResolver);
return new HttpRequestValuesInitializer(httpMethod, url, contentType, return new HttpRequestValuesInitializer(
acceptableMediaTypes, headers, requestValuesSupplier); httpMethod, url, contentType, acceptableMediaTypes, headers, requestValuesSupplier);
} }
@Nullable @Nullable
@ -296,48 +287,42 @@ final class HttpServiceMethod {
return null; return null;
} }
private static MultiValueMap<String, String> parseHeaders(String[] headersArray, private static MultiValueMap<String, String> initHeaders(
@Nullable HttpExchange typeAnnotation, HttpExchange methodAnnotation,
@Nullable StringValueResolver embeddedValueResolver) { @Nullable StringValueResolver embeddedValueResolver) {
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>(); MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
for (String h: headersArray) { if (typeAnnotation != null) {
String[] headerPair = StringUtils.split(h, "="); addHeaders(typeAnnotation.headers(), embeddedValueResolver, headers);
if (headerPair != null) {
String headerName = headerPair[0].trim();
List<String> headerValues = new ArrayList<>();
Set<String> parsedValues = StringUtils.commaDelimitedListToSet(headerPair[1]);
for (String headerValue : parsedValues) {
if (embeddedValueResolver != null) {
headerValue = embeddedValueResolver.resolveStringValue(headerValue);
}
if (headerValue != null) {
headerValue = headerValue.trim();
headerValues.add(headerValue);
}
}
if (!headerValues.isEmpty()) {
headers.addAll(headerName, headerValues);
}
}
} }
addHeaders(methodAnnotation.headers(), embeddedValueResolver, headers);
return headers; return headers;
} }
@Nullable private static void addHeaders(
private static MultiValueMap<String, String> initHeaders(@Nullable HttpExchange typeAnnotation, HttpExchange methodAnnotation, String[] rawValues, @Nullable StringValueResolver embeddedValueResolver,
@Nullable StringValueResolver embeddedValueResolver) { MultiValueMap<String, String> outputHeaders) {
MultiValueMap<String, String> methodLevelHeaders = parseHeaders(methodAnnotation.headers(),
embeddedValueResolver);
if (!ObjectUtils.isEmpty(methodLevelHeaders)) {
return methodLevelHeaders;
}
MultiValueMap<String, String> typeLevelHeaders = (typeAnnotation != null ? for (String rawValue: rawValues) {
parseHeaders(typeAnnotation.headers(), embeddedValueResolver) : null); String[] pair = StringUtils.split(rawValue, "=");
if (!ObjectUtils.isEmpty(typeLevelHeaders)) { if (pair == null) {
return typeLevelHeaders; continue;
}
String name = pair[0].trim();
List<String> values = new ArrayList<>();
for (String value : StringUtils.commaDelimitedListToSet(pair[1])) {
if (embeddedValueResolver != null) {
value = embeddedValueResolver.resolveStringValue(value);
}
if (value != null) {
value = value.trim();
values.add(value);
}
}
if (!values.isEmpty()) {
outputHeaders.addAll(name, values);
}
} }
return null;
} }
private static List<AnnotationDescriptor> getAnnotationDescriptors(AnnotatedElement element) { private static List<AnnotationDescriptor> getAnnotationDescriptors(AnnotatedElement element) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -348,8 +348,10 @@ class HttpServiceMethodTests {
@PostExchange(url = "/url", contentType = APPLICATION_JSON_VALUE, accept = APPLICATION_JSON_VALUE) @PostExchange(url = "/url", contentType = APPLICATION_JSON_VALUE, accept = APPLICATION_JSON_VALUE)
void performPost(); void performPost();
@HttpExchange(contentType = APPLICATION_JSON_VALUE, headers = {"CustomHeader=a,b, c", @HttpExchange(
"Content-Type=" + APPLICATION_NDJSON_VALUE}, method = "GET") method = "GET",
contentType = APPLICATION_JSON_VALUE,
headers = {"CustomHeader=a,b, c", "Content-Type=" + APPLICATION_NDJSON_VALUE})
void performGetWithHeaders(); void performGetWithHeaders();
} }

View File

@ -430,7 +430,8 @@ class RequestMappingHandlerMappingTests {
@PostExchange(url = "/custom", contentType = "application/json", accept = "text/plain;charset=UTF-8") @PostExchange(url = "/custom", contentType = "application/json", accept = "text/plain;charset=UTF-8")
public void customValuesExchange(){} public void customValuesExchange(){}
@HttpExchange(method="GET", url = "/headers", @HttpExchange(
method="GET", url = "/headers",
headers = {"h1=hv1", "!h2", "Accept=application/ignored"}) headers = {"h1=hv1", "!h2", "Accept=application/ignored"})
public String customHeadersExchange() { public String customHeadersExchange() {
return "info"; return "info";