Support custom HTTP method for @HttpExchange
Closes gh-28504
This commit is contained in:
parent
48c1746693
commit
ff890bc1cc
|
@ -1,57 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
* 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.web.service.annotation;
|
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
import org.springframework.core.annotation.AliasFor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for {@link HttpExchange @HttpExchange} for HTTP HEAD requests.
|
|
||||||
*
|
|
||||||
* @author Rossen Stoyanchev
|
|
||||||
* @since 6.0
|
|
||||||
*/
|
|
||||||
@Target(ElementType.METHOD)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Documented
|
|
||||||
@HttpExchange(method = "HEAD")
|
|
||||||
public @interface HeadExchange {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link HttpExchange#value}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = HttpExchange.class)
|
|
||||||
String value() default "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link HttpExchange#url()}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = HttpExchange.class)
|
|
||||||
String url() default "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link HttpExchange#accept()}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = HttpExchange.class)
|
|
||||||
String[] accept() default {};
|
|
||||||
|
|
||||||
}
|
|
|
@ -26,13 +26,16 @@ import org.springframework.core.annotation.AliasFor;
|
||||||
import org.springframework.web.bind.annotation.Mapping;
|
import org.springframework.web.bind.annotation.Mapping;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation that declares an HTTP service method as an HTTP endpoint defined
|
* Annotation to declare a method on an HTTP service interface as an HTTP
|
||||||
* through attributes of the annotation and method argument values.
|
* endpoint. The endpoint details are defined statically through attributes of
|
||||||
|
* the annotation, as well as through the input method argument values.
|
||||||
*
|
*
|
||||||
* <p>The annotation may only be used at the type level — for example to
|
* <p>Supported at the type level to express common attributes, to be inherited
|
||||||
* specify a base URL path. At the method level, use one of the HTTP method
|
* by all methods, such as a base URL path.
|
||||||
* specific, shortcut annotations, each of which is <em>meta-annotated</em> with
|
*
|
||||||
* {@code HttpExchange}:
|
* <p>At the method level, it's more common to use one of the below HTTP method
|
||||||
|
* specific, shortcut annotation, each of which is itself <em>meta-annotated</em>
|
||||||
|
* with {@code HttpExchange}:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link GetExchange}
|
* <li>{@link GetExchange}
|
||||||
|
@ -40,8 +43,6 @@ import org.springframework.web.bind.annotation.Mapping;
|
||||||
* <li>{@link PutExchange}
|
* <li>{@link PutExchange}
|
||||||
* <li>{@link PatchExchange}
|
* <li>{@link PatchExchange}
|
||||||
* <li>{@link DeleteExchange}
|
* <li>{@link DeleteExchange}
|
||||||
* <li>{@link OptionsExchange}
|
|
||||||
* <li>{@link HeadExchange}
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>Supported method arguments:
|
* <p>Supported method arguments:
|
||||||
|
@ -100,7 +101,7 @@ import org.springframework.web.bind.annotation.Mapping;
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @since 6.0
|
* @since 6.0
|
||||||
*/
|
*/
|
||||||
@Target(ElementType.TYPE)
|
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Documented
|
@Documented
|
||||||
@Mapping
|
@Mapping
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
* 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.web.service.annotation;
|
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
import org.springframework.core.annotation.AliasFor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for {@link HttpExchange @HttpExchange} for HTTP OPTIONS requests.
|
|
||||||
*
|
|
||||||
* @author Rossen Stoyanchev
|
|
||||||
* @since 6.0
|
|
||||||
*/
|
|
||||||
@Target(ElementType.METHOD)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Documented
|
|
||||||
@HttpExchange(method = "OPTIONS")
|
|
||||||
public @interface OptionsExchange {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link HttpExchange#value}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = HttpExchange.class)
|
|
||||||
String value() default "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for {@link HttpExchange#url()}.
|
|
||||||
*/
|
|
||||||
@AliasFor(annotation = HttpExchange.class)
|
|
||||||
String url() default "";
|
|
||||||
|
|
||||||
}
|
|
|
@ -22,12 +22,14 @@ import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.core.MethodParameter;
|
import org.springframework.core.MethodParameter;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link HttpServiceArgumentResolver} that resolves the target
|
* {@link HttpServiceArgumentResolver} that resolves the target
|
||||||
* request's HTTP method from an {@link HttpMethod} argument.
|
* request's HTTP method from an {@link HttpMethod} argument.
|
||||||
*
|
*
|
||||||
* @author Olga Maciaszek-Sharma
|
* @author Olga Maciaszek-Sharma
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
* @since 6.0
|
* @since 6.0
|
||||||
*/
|
*/
|
||||||
public class HttpMethodArgumentResolver implements HttpServiceArgumentResolver {
|
public class HttpMethodArgumentResolver implements HttpServiceArgumentResolver {
|
||||||
|
@ -43,13 +45,12 @@ public class HttpMethodArgumentResolver implements HttpServiceArgumentResolver {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument != null) {
|
Assert.notNull(argument, "HttpMethod is required");
|
||||||
HttpMethod httpMethod = (HttpMethod) argument;
|
HttpMethod httpMethod = (HttpMethod) argument;
|
||||||
requestValues.setHttpMethod(httpMethod);
|
requestValues.setHttpMethod(httpMethod);
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("Resolved HTTP method to: " + httpMethod.name());
|
logger.trace("Resolved HTTP method to: " + httpMethod.name());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@ public final class HttpRequestValues {
|
||||||
CollectionUtils.toMultiValueMap(Collections.emptyMap());
|
CollectionUtils.toMultiValueMap(Collections.emptyMap());
|
||||||
|
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private final HttpMethod httpMethod;
|
private final HttpMethod httpMethod;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -82,7 +83,7 @@ public final class HttpRequestValues {
|
||||||
private final ParameterizedTypeReference<?> bodyElementType;
|
private final ParameterizedTypeReference<?> bodyElementType;
|
||||||
|
|
||||||
|
|
||||||
private HttpRequestValues(HttpMethod httpMethod,
|
private HttpRequestValues(@Nullable HttpMethod httpMethod,
|
||||||
@Nullable URI uri, @Nullable String uriTemplate, Map<String, String> uriVariables,
|
@Nullable URI uri, @Nullable String uriTemplate, Map<String, String> uriVariables,
|
||||||
HttpHeaders headers, MultiValueMap<String, String> cookies, Map<String, Object> attributes,
|
HttpHeaders headers, MultiValueMap<String, String> cookies, Map<String, Object> attributes,
|
||||||
@Nullable Object bodyValue,
|
@Nullable Object bodyValue,
|
||||||
|
@ -106,6 +107,7 @@ public final class HttpRequestValues {
|
||||||
/**
|
/**
|
||||||
* Return the HTTP method to use for the request.
|
* Return the HTTP method to use for the request.
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
public HttpMethod getHttpMethod() {
|
public HttpMethod getHttpMethod() {
|
||||||
return this.httpMethod;
|
return this.httpMethod;
|
||||||
}
|
}
|
||||||
|
@ -187,8 +189,8 @@ public final class HttpRequestValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Builder builder(HttpMethod httpMethod) {
|
public static Builder builder() {
|
||||||
return new Builder(httpMethod);
|
return new Builder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -199,6 +201,7 @@ public final class HttpRequestValues {
|
||||||
|
|
||||||
private static final Function<MultiValueMap<String, String>, byte[]> FORM_DATA_SERIALIZER = new FormDataSerializer();
|
private static final Function<MultiValueMap<String, String>, byte[]> FORM_DATA_SERIALIZER = new FormDataSerializer();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private HttpMethod httpMethod;
|
private HttpMethod httpMethod;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -231,16 +234,10 @@ public final class HttpRequestValues {
|
||||||
@Nullable
|
@Nullable
|
||||||
private ParameterizedTypeReference<?> bodyElementType;
|
private ParameterizedTypeReference<?> bodyElementType;
|
||||||
|
|
||||||
private Builder(HttpMethod httpMethod) {
|
|
||||||
Assert.notNull(httpMethod, "HttpMethod is required");
|
|
||||||
this.httpMethod = httpMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the HTTP method for the request.
|
* Set the HTTP method for the request.
|
||||||
*/
|
*/
|
||||||
public Builder setHttpMethod(HttpMethod httpMethod) {
|
public Builder setHttpMethod(HttpMethod httpMethod) {
|
||||||
Assert.notNull(httpMethod, "HttpMethod is required");
|
|
||||||
this.httpMethod = httpMethod;
|
this.httpMethod = httpMethod;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,7 +131,7 @@ final class HttpServiceMethod {
|
||||||
* and method-level {@link HttpExchange @HttpRequest} annotations.
|
* and method-level {@link HttpExchange @HttpRequest} annotations.
|
||||||
*/
|
*/
|
||||||
private record HttpRequestValuesInitializer(
|
private record HttpRequestValuesInitializer(
|
||||||
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) {
|
||||||
|
|
||||||
private HttpRequestValuesInitializer(
|
private HttpRequestValuesInitializer(
|
||||||
|
@ -145,7 +145,10 @@ final class HttpServiceMethod {
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpRequestValues.Builder initializeRequestValuesBuilder() {
|
public HttpRequestValues.Builder initializeRequestValuesBuilder() {
|
||||||
HttpRequestValues.Builder requestValues = HttpRequestValues.builder(this.httpMethod);
|
HttpRequestValues.Builder requestValues = HttpRequestValues.builder();
|
||||||
|
if (this.httpMethod != null) {
|
||||||
|
requestValues.setHttpMethod(this.httpMethod);
|
||||||
|
}
|
||||||
if (this.url != null) {
|
if (this.url != null) {
|
||||||
requestValues.setUriTemplate(this.url);
|
requestValues.setUriTemplate(this.url);
|
||||||
}
|
}
|
||||||
|
@ -178,7 +181,7 @@ final class HttpServiceMethod {
|
||||||
return new HttpRequestValuesInitializer(httpMethod, url, contentType, acceptableMediaTypes);
|
return new HttpRequestValuesInitializer(httpMethod, url, contentType, acceptableMediaTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private static HttpMethod initHttpMethod(@Nullable HttpExchange typeAnnot, HttpExchange annot) {
|
private static HttpMethod initHttpMethod(@Nullable HttpExchange typeAnnot, HttpExchange annot) {
|
||||||
|
|
||||||
String value1 = (typeAnnot != null ? typeAnnot.method() : null);
|
String value1 = (typeAnnot != null ? typeAnnot.method() : null);
|
||||||
|
@ -192,7 +195,7 @@ final class HttpServiceMethod {
|
||||||
return HttpMethod.valueOf(value1);
|
return HttpMethod.valueOf(value1);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalStateException("HttpMethod is required");
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|
|
@ -20,9 +20,12 @@ import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.web.service.annotation.GetExchange;
|
import org.springframework.web.service.annotation.GetExchange;
|
||||||
|
import org.springframework.web.service.annotation.HttpExchange;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,11 +50,17 @@ public class HttpMethodArgumentResolverTests {
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void requestMethodOverride() {
|
void httpMethodFromArgument() {
|
||||||
this.service.execute(HttpMethod.POST);
|
this.service.execute(HttpMethod.POST);
|
||||||
assertThat(getActualMethod()).isEqualTo(HttpMethod.POST);
|
assertThat(getActualMethod()).isEqualTo(HttpMethod.POST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void httpMethodFromAnnotation() {
|
||||||
|
this.service.executeHttpHead();
|
||||||
|
assertThat(getActualMethod()).isEqualTo(HttpMethod.HEAD);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void notHttpMethod() {
|
void notHttpMethod() {
|
||||||
assertThatIllegalStateException()
|
assertThatIllegalStateException()
|
||||||
|
@ -63,11 +72,11 @@ public class HttpMethodArgumentResolverTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void ignoreNull() {
|
void nullHttpMethod() {
|
||||||
this.service.execute(null);
|
assertThatIllegalArgumentException().isThrownBy(() -> this.service.execute(null));
|
||||||
assertThat(getActualMethod()).isEqualTo(HttpMethod.GET);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private HttpMethod getActualMethod() {
|
private HttpMethod getActualMethod() {
|
||||||
return this.client.getRequestValues().getHttpMethod();
|
return this.client.getRequestValues().getHttpMethod();
|
||||||
}
|
}
|
||||||
|
@ -75,9 +84,12 @@ public class HttpMethodArgumentResolverTests {
|
||||||
|
|
||||||
private interface Service {
|
private interface Service {
|
||||||
|
|
||||||
@GetExchange
|
@HttpExchange
|
||||||
void execute(HttpMethod method);
|
void execute(HttpMethod method);
|
||||||
|
|
||||||
|
@HttpExchange(method = "HEAD")
|
||||||
|
void executeHttpHead();
|
||||||
|
|
||||||
@GetExchange
|
@GetExchange
|
||||||
void executeNotHttpMethod(String test);
|
void executeNotHttpMethod(String test);
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ public class HttpRequestValuesTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void defaultUri() {
|
void defaultUri() {
|
||||||
HttpRequestValues requestValues = HttpRequestValues.builder(HttpMethod.GET).build();
|
HttpRequestValues requestValues = HttpRequestValues.builder().setHttpMethod(HttpMethod.GET).build();
|
||||||
|
|
||||||
assertThat(requestValues.getUri()).isNull();
|
assertThat(requestValues.getUri()).isNull();
|
||||||
assertThat(requestValues.getUriTemplate()).isEqualTo("");
|
assertThat(requestValues.getUriTemplate()).isEqualTo("");
|
||||||
|
@ -48,7 +48,7 @@ public class HttpRequestValuesTests {
|
||||||
@ValueSource(strings = {"POST", "PUT", "PATCH"})
|
@ValueSource(strings = {"POST", "PUT", "PATCH"})
|
||||||
void requestParamAsFormData(String httpMethod) {
|
void requestParamAsFormData(String httpMethod) {
|
||||||
|
|
||||||
HttpRequestValues requestValues = HttpRequestValues.builder(HttpMethod.valueOf(httpMethod))
|
HttpRequestValues requestValues = HttpRequestValues.builder().setHttpMethod(HttpMethod.valueOf(httpMethod))
|
||||||
.setContentType(MediaType.APPLICATION_FORM_URLENCODED)
|
.setContentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
.addRequestParameter("param1", "1st value")
|
.addRequestParameter("param1", "1st value")
|
||||||
.addRequestParameter("param2", "2nd value A", "2nd value B")
|
.addRequestParameter("param2", "2nd value A", "2nd value B")
|
||||||
|
@ -62,7 +62,7 @@ public class HttpRequestValuesTests {
|
||||||
@Test
|
@Test
|
||||||
void requestParamAsQueryParamsInUriTemplate() {
|
void requestParamAsQueryParamsInUriTemplate() {
|
||||||
|
|
||||||
HttpRequestValues requestValues = HttpRequestValues.builder(HttpMethod.POST)
|
HttpRequestValues requestValues = HttpRequestValues.builder().setHttpMethod(HttpMethod.POST)
|
||||||
.setUriTemplate("/path")
|
.setUriTemplate("/path")
|
||||||
.addRequestParameter("param1", "1st value")
|
.addRequestParameter("param1", "1st value")
|
||||||
.addRequestParameter("param2", "2nd value A", "2nd value B")
|
.addRequestParameter("param2", "2nd value A", "2nd value B")
|
||||||
|
@ -96,7 +96,7 @@ public class HttpRequestValuesTests {
|
||||||
@Test
|
@Test
|
||||||
void requestParamAsQueryParamsInUri() {
|
void requestParamAsQueryParamsInUri() {
|
||||||
|
|
||||||
HttpRequestValues requestValues = HttpRequestValues.builder(HttpMethod.POST)
|
HttpRequestValues requestValues = HttpRequestValues.builder().setHttpMethod(HttpMethod.POST)
|
||||||
.setUri(URI.create("/path"))
|
.setUri(URI.create("/path"))
|
||||||
.addRequestParameter("param1", "1st value")
|
.addRequestParameter("param1", "1st value")
|
||||||
.addRequestParameter("param2", "2nd value A", "2nd value B")
|
.addRequestParameter("param2", "2nd value A", "2nd value B")
|
||||||
|
|
|
@ -86,7 +86,7 @@ public class WebClientAdapter implements HttpClientAdapter {
|
||||||
private WebClient.RequestBodySpec toBodySpec(HttpRequestValues requestValues) {
|
private WebClient.RequestBodySpec toBodySpec(HttpRequestValues requestValues) {
|
||||||
|
|
||||||
HttpMethod httpMethod = requestValues.getHttpMethod();
|
HttpMethod httpMethod = requestValues.getHttpMethod();
|
||||||
Assert.notNull(httpMethod, "No HttpMethod");
|
Assert.notNull(httpMethod, "HttpMethod is required");
|
||||||
|
|
||||||
WebClient.RequestBodyUriSpec uriSpec = this.webClient.method(httpMethod);
|
WebClient.RequestBodyUriSpec uriSpec = this.webClient.method(httpMethod);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue