diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index c1b14837..72afb5ec 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -32,4 +32,7 @@ jobs: restore-keys: | ${{ runner.os }}-maven- - name: Build with Maven - run: ./mvnw clean install -B -U + run: ./mvnw clean install -B -U -P sonar + - uses: codecov/codecov-action@v1 + with: + fail_ci_if_error: true diff --git a/pom.xml b/pom.xml index d0f12008..38899ff8 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ ${basedir} 2.11.3 - 3.0.3-SNAPSHOT + 3.1.0-SNAPSHOT 2.10 diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java index 1bc64b15..ef290045 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java @@ -382,11 +382,9 @@ public class FeignClientFactoryBean Feign.Builder builder = feign(context); if (!StringUtils.hasText(url)) { - if (url != null && LOG.isWarnEnabled()) { - LOG.warn("The provided URL is empty. Will try picking an instance via load-balancing."); - } - else if (LOG.isDebugEnabled()) { - LOG.debug("URL not provided. Will use LoadBalancer."); + + if (LOG.isInfoEnabled()) { + LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing."); } if (!name.startsWith("http")) { url = "http://" + name; diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/MatrixVariableParameterProcessor.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/MatrixVariableParameterProcessor.java index d8b3a827..ea26f3c5 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/MatrixVariableParameterProcessor.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/MatrixVariableParameterProcessor.java @@ -72,8 +72,8 @@ public class MatrixVariableParameterProcessor implements AnnotatedParameterProce private String expandMap(Object object) { Map paramMap = (Map) object; - return paramMap.keySet().stream().map(key -> ";" + key + "=" + paramMap.get(key).toString()) - .collect(Collectors.joining()); + return paramMap.keySet().stream().filter(key -> paramMap.get(key) != null) + .map(key -> ";" + key + "=" + paramMap.get(key).toString()).collect(Collectors.joining()); } } diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/LoadBalancerResponseStatusCodeException.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/LoadBalancerResponseStatusCodeException.java new file mode 100644 index 00000000..c1ac711e --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/LoadBalancerResponseStatusCodeException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013-2020 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.cloud.openfeign.loadbalancer; + +import java.io.ByteArrayInputStream; +import java.net.URI; + +import feign.Response; + +import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException; + +/** + * A {@link RetryableStatusCodeException} for {@link Response}s. + * + * @author Ryan Baxter + */ +public class LoadBalancerResponseStatusCodeException extends RetryableStatusCodeException { + + private final Response response; + + public LoadBalancerResponseStatusCodeException(String serviceId, Response response, byte[] body, URI uri) { + super(serviceId, response.status(), response, uri); + this.response = Response.builder().body(new ByteArrayInputStream(body), body.length).headers(response.headers()) + .reason(response.reason()).status(response.status()).request(response.request()).build(); + } + + @Override + public Response getResponse() { + return this.response; + } + +} diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClient.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClient.java index 0be4ea3d..6883a179 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClient.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClient.java @@ -46,7 +46,6 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValida import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.ResponseData; import org.springframework.cloud.client.loadbalancer.RetryableRequestContext; -import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -57,6 +56,7 @@ import org.springframework.retry.backoff.NoBackOffPolicy; import org.springframework.retry.policy.NeverRetryPolicy; import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; +import org.springframework.util.StreamUtils; import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils.buildRequestData; @@ -161,8 +161,11 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client { if (LOG.isDebugEnabled()) { LOG.debug(String.format("Retrying on status code: %d", responseStatus)); } + byte[] byteArray = response.body() == null ? new byte[] {} + : StreamUtils.copyToByteArray(response.body().asInputStream()); response.close(); - throw new RetryableStatusCodeException(serviceId, responseStatus, response, URI.create(request.url())); + throw new LoadBalancerResponseStatusCodeException(serviceId, response, byteArray, + URI.create(request.url())); } return response; }, new LoadBalancedRecoveryCallback() { diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringEncoder.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringEncoder.java index 1eff6374..fd061390 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringEncoder.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringEncoder.java @@ -25,6 +25,7 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.stream.Stream; import feign.RequestTemplate; @@ -50,6 +51,10 @@ import org.springframework.web.multipart.MultipartFile; import static org.springframework.cloud.openfeign.support.FeignUtils.getHeaders; import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders; +import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA; +import static org.springframework.http.MediaType.MULTIPART_MIXED; +import static org.springframework.http.MediaType.MULTIPART_RELATED; /** * @author Spencer Gibb @@ -58,6 +63,7 @@ import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHead * @author Aaron Whiteside * @author Darren Foong * @author Olga Maciaszek-Sharma + * @author Can Bezmen */ @SuppressWarnings("rawtypes") public class SpringEncoder implements Encoder { @@ -115,7 +121,7 @@ public class SpringEncoder implements Encoder { requestContentType = MediaType.valueOf(type); } - if (isMultipartType(requestContentType)) { + if (isFormRelatedContentType(requestContentType)) { springFormEncoder.encode(requestBody, bodyType, request); return; } @@ -229,9 +235,16 @@ public class SpringEncoder implements Encoder { } } + private boolean isFormRelatedContentType(MediaType requestContentType) { + return isMultipartType(requestContentType) || isFormUrlEncoded(requestContentType); + } + private boolean isMultipartType(MediaType requestContentType) { - return Arrays.asList(MediaType.MULTIPART_FORM_DATA, MediaType.MULTIPART_MIXED, MediaType.MULTIPART_RELATED) - .contains(requestContentType); + return Arrays.asList(MULTIPART_FORM_DATA, MULTIPART_MIXED, MULTIPART_RELATED).contains(requestContentType); + } + + private boolean isFormUrlEncoded(MediaType requestContentType) { + return Objects.equals(APPLICATION_FORM_URLENCODED, requestContentType); } private boolean binaryContentType(FeignOutputMessage outputMessage) { diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClientTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClientTests.java index a5573434..c8c7de61 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClientTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClientTests.java @@ -16,7 +16,10 @@ package org.springframework.cloud.openfeign.loadbalancer; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Collection; @@ -29,6 +32,7 @@ import java.util.concurrent.ConcurrentHashMap; import feign.Client; import feign.Request; import feign.Response; +import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -117,6 +121,13 @@ class RetryableFeignBlockingLoadBalancerClientTests { return Response.builder().request(testRequest()).status(status).build(); } + private Response testResponse(int status, String body) { + // ByteArrayInputStream ignores close() and must be wrapped + InputStream reallyCloseable = new BufferedInputStream( + new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8))); + return Response.builder().request(testRequest()).status(status).body(reallyCloseable, null).build(); + } + @Test void shouldExecuteOriginalRequestIfInstanceNotFound() throws IOException { Request request = testRequest(); @@ -148,6 +159,22 @@ class RetryableFeignBlockingLoadBalancerClientTests { verify(delegate, times(2)).execute(any(), any()); } + @Test + void shouldExposeResponseBodyOnRetry() throws IOException { + properties.getRetry().getRetryableStatusCodes().add(503); + Request request = testRequest(); + when(delegate.execute(any(), any())).thenReturn(testResponse(503, "foo"), testResponse(503, "foo")); + when(retryFactory.createRetryPolicy(any(), eq(loadBalancerClient))) + .thenReturn(new BlockingLoadBalancedRetryPolicy(properties)); + when(loadBalancerClient.reconstructURI(serviceInstance, URI.create("http://test/path"))) + .thenReturn(URI.create("http://testhost:80/path")); + + Response response = feignBlockingLoadBalancerClient.execute(request, new Request.Options()); + + String bodyContent = IOUtils.toString(response.body().asReader(StandardCharsets.UTF_8)); + assertThat(bodyContent).isEqualTo("foo"); + } + @Test void shouldPassCorrectRequestToDelegate() throws IOException { Request request = testRequest(); diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringEncoderTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringEncoderTests.java index 5b30e213..600d2e88 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringEncoderTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringEncoderTests.java @@ -60,6 +60,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.http.HttpHeaders.ACCEPT; import static org.springframework.http.HttpHeaders.CONTENT_LENGTH; import static org.springframework.http.HttpHeaders.CONTENT_TYPE; +import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE; import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; @@ -68,6 +69,7 @@ import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; * @author Spencer Gibb * @author Olga Maciaszek-Sharma * @author Ahmad Mozafarnia + * @author Can Bezmen */ @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = SpringEncoderTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT, @@ -179,6 +181,17 @@ public class SpringEncoderTests { assertThat(new String(request.requestBody().asBytes())).as("Body content cannot be decoded").contains("hi"); } + @Test + public void testFromURLEncodedValue() { + Encoder encoder = context.getInstance("formUrlEncoded", Encoder.class); + assertThat(encoder).isNotNull(); + RequestTemplate request = new RequestTemplate(); + request.header(CONTENT_TYPE, APPLICATION_FORM_URLENCODED_VALUE); + String body = "test"; + encoder.encode(body, String.class, request); + assertThat(new String(request.requestBody().asBytes())).as("Body content cannot be decoded").contains(body); + } + @Test public void testNoCharsetForBinaryFiles() { Encoder encoder = context.getInstance("test", Encoder.class); diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/ValidFeignClientTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/ValidFeignClientTests.java index 83b97771..be7ba519 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/ValidFeignClientTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/ValidFeignClientTests.java @@ -38,7 +38,6 @@ import javax.servlet.http.Part; import feign.Client; import feign.Logger; import feign.RequestInterceptor; -import feign.RequestTemplate; import feign.codec.EncodeException; import org.junit.jupiter.api.Test; @@ -65,7 +64,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.format.Formatter; -import org.springframework.format.FormatterRegistry; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpEntity; import org.springframework.http.HttpStatus; @@ -73,6 +71,8 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; @@ -84,6 +84,9 @@ import org.springframework.web.multipart.MultipartFile; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE; +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; +import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; /** * @author Spencer Gibb @@ -277,6 +280,13 @@ class ValidFeignClientTests { assertThat(testClient.getToString(args)).isEqualTo(expectedResult); } + @Test + void testFormURLEncoded() { + Hello hello = new Hello(HELLO_WORLD_1); + Hello response = testClient.postFormUrlEncoded(hello); + assertThat(response).isEqualTo(hello); + } + @Test void namedFeignClientWorks() { assertThat(namedFeignClient).as("namedFeignClient was null").isNotNull(); @@ -382,52 +392,42 @@ class ValidFeignClientTests { @FeignClient(name = "localapp8") protected interface MultipartClient { - @RequestMapping(method = RequestMethod.POST, path = "/singlePart", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/singlePart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String singlePart(@RequestPart("hello") String hello); - @RequestMapping(method = RequestMethod.POST, path = "/singlePojoPart", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/singlePojoPart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String singlePojoPart(@RequestPart("hello") Hello hello); - @RequestMapping(method = RequestMethod.POST, path = "/multipart", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String multipart(@RequestPart("hello") String hello, @RequestPart("world") String world, @RequestPart("file") MultipartFile file); - @RequestMapping(method = RequestMethod.POST, path = "/multipartPojo", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartPojo", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String multipartPojo(@RequestPart("hello") String hello, @RequestPart("world") String world, @RequestPart("pojo1") Hello pojo1, @RequestPart("pojo2") Hello pojo2, @RequestPart("file") MultipartFile file); - @RequestMapping(method = RequestMethod.POST, path = "/multipartNames", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String requestPartListOfMultipartFilesReturnsPartNames(@RequestPart("files") List files); - @RequestMapping(method = RequestMethod.POST, path = "/multipartFilenames", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartFilenames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String requestPartListOfMultipartFilesReturnsFileNames(@RequestPart("files") List files); - @RequestMapping(method = RequestMethod.POST, path = "/multipartPojosFiles", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartPojosFiles", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String requestPartListOfPojosAndListOfMultipartFiles(@RequestPart("pojos") List pojos, @RequestPart("files") List files); - @RequestMapping(method = RequestMethod.POST, path = "/multipartNames", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String requestBodyListOfMultipartFiles(@RequestBody List files); - @RequestMapping(method = RequestMethod.POST, path = "/multipartNames", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String requestBodySingleMultipartFile(@RequestBody MultipartFile file); - @RequestMapping(method = RequestMethod.POST, path = "/multipartNames", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String requestBodyMap(@RequestBody Map form); - @RequestMapping(method = RequestMethod.POST, path = "/invalid", - consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/invalid", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE, + produces = TEXT_PLAIN_VALUE) String invalid(@RequestBody MultipartFile file); } @@ -435,36 +435,36 @@ class ValidFeignClientTests { @FeignClient(name = "localapp", configuration = TestClientConfig.class) protected interface TestClient { - @RequestMapping(method = RequestMethod.GET, path = "/hello") + @GetMapping("/hello") Hello getHello(); - @RequestMapping(method = RequestMethod.GET, path = "/hello") + @GetMapping("/hello") Optional getOptionalHello(); - @RequestMapping(method = RequestMethod.GET, path = "${feignClient.methodLevelRequestMappingPath}") + @GetMapping("${feignClient.methodLevelRequestMappingPath}") Hello getHelloUsingPropertyPlaceHolder(); - @RequestMapping(method = RequestMethod.GET, path = "/hellos") + @GetMapping("/hellos") List getHellos(); - @RequestMapping(method = RequestMethod.GET, path = "/hellostrings") + @GetMapping("/hellostrings") List getHelloStrings(); - @RequestMapping(method = RequestMethod.GET, path = "/helloheaders") + @GetMapping("/helloheaders") List getHelloHeaders(); - @RequestMapping(method = RequestMethod.GET, path = "/helloheadersplaceholders", + @GetMapping(path = "/helloheadersplaceholders", headers = "myPlaceholderHeader=${feignClient.myPlaceholderHeader}") String getHelloHeadersPlaceholders(); - @RequestMapping(method = RequestMethod.GET, path = "/helloparams") + @GetMapping("/helloparams") List getParams(@RequestParam("params") List params); - @RequestMapping(method = RequestMethod.GET, path = "/formattedparams") + @GetMapping("/formattedparams") List getFormattedParams( @RequestParam("params") @DateTimeFormat(pattern = "dd-MM-yyyy") List params); - @RequestMapping(method = RequestMethod.GET, path = "/noContent") + @GetMapping("/noContent") ResponseEntity noContent(); @RequestMapping(method = RequestMethod.HEAD, path = "/head") @@ -477,21 +477,24 @@ class ValidFeignClientTests { produces = "application/vnd.io.spring.cloud.test.v1+json", path = "/complex") String moreComplexContentType(String body); - @RequestMapping(method = RequestMethod.GET, path = "/tostring") + @GetMapping("/tostring") String getToString(@RequestParam("arg") Arg arg); - @RequestMapping(method = RequestMethod.GET, path = "/tostring2") + @GetMapping("/tostring2") String getToString(@RequestParam("arg") OtherArg arg); - @RequestMapping(method = RequestMethod.GET, path = "/tostringcollection") + @GetMapping("/tostringcollection") Collection getToString(@RequestParam("arg") Collection args); + @PostMapping(path = "/form-urlencoded", consumes = APPLICATION_FORM_URLENCODED_VALUE) + Hello postFormUrlEncoded(Hello hello); + } @FeignClient(name = "localapp1") protected interface TestClientServiceId { - @RequestMapping(method = RequestMethod.GET, path = "/hello") + @GetMapping("/hello") Hello getHello(); } @@ -499,10 +502,10 @@ class ValidFeignClientTests { @FeignClient(name = "localapp2", decode404 = true) protected interface DecodingTestClient { - @RequestMapping(method = RequestMethod.GET, path = "/notFound") + @GetMapping("/notFound") ResponseEntity notFound(); - @RequestMapping(method = RequestMethod.GET, path = "/notFound") + @GetMapping("/notFound") Optional optional(); } @@ -526,22 +529,12 @@ class ValidFeignClientTests { @Bean public RequestInterceptor interceptor1() { - return new RequestInterceptor() { - @Override - public void apply(RequestTemplate template) { - template.header(MYHEADER1, "myheader1value"); - } - }; + return template -> template.header(MYHEADER1, "myheader1value"); } @Bean public RequestInterceptor interceptor2() { - return new RequestInterceptor() { - @Override - public void apply(RequestTemplate template) { - template.header(MYHEADER2, "myheader2value"); - } - }; + return template -> template.header(MYHEADER2, "myheader2value"); } } @@ -570,27 +563,21 @@ class ValidFeignClientTests { @Bean FeignFormatterRegistrar feignFormatterRegistrar() { - return new FeignFormatterRegistrar() { + return registry -> registry.addFormatter(new Formatter() { @Override - public void registerFormatters(FormatterRegistry registry) { - registry.addFormatter(new Formatter() { - - @Override - public String print(OtherArg object, Locale locale) { - if ("foo".equals(object.value)) { - return "bar"; - } - return object.value; - } - - @Override - public OtherArg parse(String text, Locale locale) throws ParseException { - return new OtherArg(text); - } - }); + public String print(OtherArg object, Locale locale) { + if ("foo".equals(object.value)) { + return "bar"; + } + return object.value; } - }; + + @Override + public OtherArg parse(String text, Locale locale) throws ParseException { + return new OtherArg(text); + } + }); } @Bean @@ -598,23 +585,23 @@ class ValidFeignClientTests { return new JsonFormWriter(); } - @RequestMapping(method = RequestMethod.GET, path = "/hello") + @GetMapping("/hello") public Hello getHello() { return new Hello(HELLO_WORLD_1); } - @RequestMapping(method = RequestMethod.GET, path = "/hello2") + @GetMapping("/hello2") public Hello getHello2() { return new Hello(OI_TERRA_2); } - @RequestMapping(method = RequestMethod.GET, path = "/hellos") + @GetMapping("/hellos") public List getHellos() { ArrayList hellos = getHelloList(); return hellos; } - @RequestMapping(method = RequestMethod.GET, path = "/hellostrings") + @GetMapping("/hellostrings") public List getHelloStrings() { ArrayList hellos = new ArrayList<>(); hellos.add(HELLO_WORLD_1); @@ -622,7 +609,7 @@ class ValidFeignClientTests { return hellos; } - @RequestMapping(method = RequestMethod.GET, path = "/helloheaders") + @GetMapping("/helloheaders") public List getHelloHeaders(@RequestHeader(MYHEADER1) String myheader1, @RequestHeader(MYHEADER2) String myheader2) { ArrayList headers = new ArrayList<>(); @@ -631,23 +618,23 @@ class ValidFeignClientTests { return headers; } - @RequestMapping(method = RequestMethod.GET, path = "/helloheadersplaceholders") + @GetMapping("/helloheadersplaceholders") public String getHelloHeadersPlaceholders(@RequestHeader("myPlaceholderHeader") String myPlaceholderHeader) { return myPlaceholderHeader; } - @RequestMapping(method = RequestMethod.GET, path = "/helloparams") + @GetMapping("/helloparams") public List getParams(@RequestParam("params") List params) { return params; } - @RequestMapping(method = RequestMethod.GET, path = "/formattedparams") + @GetMapping("/formattedparams") public List getFormattedParams( @RequestParam("params") @DateTimeFormat(pattern = "dd-MM-yyyy") List params) { return params; } - @RequestMapping(method = RequestMethod.GET, path = "/noContent") + @GetMapping("/noContent") ResponseEntity noContent() { return ResponseEntity.noContent().build(); } @@ -662,7 +649,7 @@ class ValidFeignClientTests { throw new RuntimeException("always fails"); } - @RequestMapping(method = RequestMethod.GET, path = "/notFound") + @GetMapping("/notFound") ResponseEntity notFound() { return ResponseEntity.status(HttpStatus.NOT_FOUND).body((String) null); } @@ -676,17 +663,17 @@ class ValidFeignClientTests { return body; } - @RequestMapping(method = RequestMethod.GET, path = "/tostring") + @GetMapping("/tostring") String getToString(@RequestParam("arg") Arg arg) { return arg.toString(); } - @RequestMapping(method = RequestMethod.GET, path = "/tostring2") + @GetMapping("/tostring2") String getToString(@RequestParam("arg") OtherArg arg) { return arg.value; } - @RequestMapping(method = RequestMethod.GET, path = "/tostringcollection") + @GetMapping("/tostringcollection") Collection getToString(@RequestParam("arg") Collection args) { List result = new ArrayList<>(); for (OtherArg arg : args) { @@ -695,47 +682,40 @@ class ValidFeignClientTests { return result; } - @RequestMapping(method = RequestMethod.POST, path = "/singlePart", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/singlePart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String singlePart(@RequestPart("hello") String hello) { return hello; } - @RequestMapping(method = RequestMethod.POST, path = "/singlePojoPart", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/singlePojoPart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String singlePojoPart(@RequestPart("hello") Hello hello) { return hello.getMessage(); } - @RequestMapping(method = RequestMethod.POST, path = "/multipart", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String multipart(@RequestPart("hello") String hello, @RequestPart("world") String world, @RequestPart("file") MultipartFile file) { return hello + world + file.getOriginalFilename(); } - @RequestMapping(method = RequestMethod.POST, path = "/multipartPojo", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartPojo", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String multipartPojo(@RequestPart("hello") String hello, @RequestPart("world") String world, @RequestPart("pojo1") Hello pojo1, @RequestPart("pojo2") Hello pojo2, @RequestPart("file") MultipartFile file) { return hello + world + pojo1.getMessage() + pojo2.getMessage() + file.getOriginalFilename(); } - @RequestMapping(method = RequestMethod.POST, path = "/multipartNames", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String multipartNames(HttpServletRequest request) throws Exception { return request.getParts().stream().map(Part::getName).collect(Collectors.joining(",")); } - @RequestMapping(method = RequestMethod.POST, path = "/multipartFilenames", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartFilenames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String multipartFilenames(HttpServletRequest request) throws Exception { return request.getParts().stream().map(Part::getSubmittedFileName).collect(Collectors.joining(",")); } - @RequestMapping(method = RequestMethod.POST, path = "/multipartPojosFiles", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartPojosFiles", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String requestPartListOfPojosAndListOfMultipartFiles(@RequestPart("pojos") List pojos, @RequestPart("files") List files) { StringBuilder result = new StringBuilder(); @@ -751,6 +731,11 @@ class ValidFeignClientTests { return result.toString(); } + @PostMapping(path = "/form-urlencoded", consumes = APPLICATION_FORM_URLENCODED_VALUE) + Hello postFormUrlEncoded(Hello hello) { + return hello; + } + } public static class Hello { diff --git a/spring-cloud-openfeign-dependencies/pom.xml b/spring-cloud-openfeign-dependencies/pom.xml index 55d19709..e0149925 100644 --- a/spring-cloud-openfeign-dependencies/pom.xml +++ b/spring-cloud-openfeign-dependencies/pom.xml @@ -6,7 +6,7 @@ spring-cloud-dependencies-parent org.springframework.cloud - 3.0.2 + 3.1.0-SNAPSHOT spring-cloud-openfeign-dependencies