From a230cf3ffbd4b66cd6f0308879790a3a74348bc1 Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Tue, 23 Apr 2024 10:16:39 +0000 Subject: [PATCH 1/3] feat : modularize httpClient Call --- httpClients/boot-restclient/pom.xml | 8 ++ .../config/RestClientConfiguration.java | 7 +- ...tion.java => MyCustomClientException.java} | 4 +- .../services/HttpClientService.java | 69 +++++++++++++++ .../bootrestclient/services/PostService.java | 85 +++++++------------ 5 files changed, 118 insertions(+), 55 deletions(-) rename httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/exception/{MyCustomRuntimeException.java => MyCustomClientException.java} (69%) create mode 100644 httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/HttpClientService.java diff --git a/httpClients/boot-restclient/pom.xml b/httpClients/boot-restclient/pom.xml index 0bc42615a..f6bbc2498 100644 --- a/httpClients/boot-restclient/pom.xml +++ b/httpClients/boot-restclient/pom.xml @@ -33,6 +33,14 @@ org.springframework.boot spring-boot-starter-validation + + org.springframework.retry + spring-retry + + + org.springframework + spring-aspects + org.springdoc springdoc-openapi-starter-webmvc-ui diff --git a/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/config/RestClientConfiguration.java b/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/config/RestClientConfiguration.java index 5cf8c540b..1b3869c67 100644 --- a/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/config/RestClientConfiguration.java +++ b/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/config/RestClientConfiguration.java @@ -16,12 +16,14 @@ import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.JdkClientHttpRequestFactory; import org.springframework.lang.NonNull; +import org.springframework.retry.annotation.EnableRetry; import org.springframework.util.StreamUtils; import org.springframework.web.client.RestClient; import org.springframework.web.util.DefaultUriBuilderFactory; @Configuration(proxyBeanMethods = false) @Slf4j +@EnableRetry public class RestClientConfiguration { @Bean @@ -90,7 +92,10 @@ HttpClient jdkClient() { @Bean JdkClientHttpRequestFactory jdkClientHttpRequestFactory(@NonNull HttpClient jdkClient) { - return new JdkClientHttpRequestFactory(jdkClient); + JdkClientHttpRequestFactory jdkClientHttpRequestFactory = + new JdkClientHttpRequestFactory(jdkClient); + jdkClientHttpRequestFactory.setReadTimeout(Duration.ofSeconds(60)); + return jdkClientHttpRequestFactory; } // BufferingClientHttpRequestFactory allows us to read the response body multiple times for a diff --git a/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/exception/MyCustomRuntimeException.java b/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/exception/MyCustomClientException.java similarity index 69% rename from httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/exception/MyCustomRuntimeException.java rename to httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/exception/MyCustomClientException.java index 4bf905f4c..6b42a9d30 100644 --- a/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/exception/MyCustomRuntimeException.java +++ b/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/exception/MyCustomClientException.java @@ -5,11 +5,11 @@ import org.springframework.http.HttpStatusCode; @Getter -public class MyCustomRuntimeException extends RuntimeException { +public class MyCustomClientException extends RuntimeException { private final HttpStatusCode statusCode; private final HttpHeaders headers; - public MyCustomRuntimeException(HttpStatusCode statusCode, HttpHeaders headers) { + public MyCustomClientException(HttpStatusCode statusCode, HttpHeaders headers) { this.statusCode = statusCode; this.headers = headers; } diff --git a/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/HttpClientService.java b/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/HttpClientService.java new file mode 100644 index 000000000..b3232d752 --- /dev/null +++ b/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/HttpClientService.java @@ -0,0 +1,69 @@ +package com.example.restclient.bootrestclient.services; + +import com.example.restclient.bootrestclient.exception.MyCustomClientException; +import java.net.URI; +import java.util.function.Function; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatusCode; +import org.springframework.lang.Nullable; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Retryable; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; +import org.springframework.web.util.UriBuilder; + +@Service +@Retryable( + retryFor = {Exception.class}, + maxAttempts = 3, + backoff = @Backoff(delay = 5000)) +public class HttpClientService { + + private final RestClient restClient; + + public HttpClientService(RestClient restClient) { + this.restClient = restClient; + } + + T callAndFetchResponse( + Function uriFunction, + HttpMethod httpMethod, + @Nullable Object body, + Class bodyType) { + return callServer(uriFunction, httpMethod, body, bodyType, null); + } + + T callAndFetchResponse( + Function uriFunction, + HttpMethod httpMethod, + @Nullable Object body, + ParameterizedTypeReference bodyType) { + return callServer(uriFunction, httpMethod, body, null, bodyType); + } + + private T callServer( + Function uriFunction, + HttpMethod httpMethod, + Object body, + Class bodyType, + ParameterizedTypeReference typeReferenceBodyType) { + RestClient.RequestBodySpec uri = restClient.method(httpMethod).uri(uriFunction); + if (body != null) { + uri.body(body); + } + RestClient.ResponseSpec responseSpec = + uri.retrieve() + .onStatus( + HttpStatusCode::is4xxClientError, + (request, response) -> { + throw new MyCustomClientException( + response.getStatusCode(), response.getHeaders()); + }); + if (bodyType != null) { + return responseSpec.body(bodyType); + } else { + return responseSpec.body(typeReferenceBodyType); + } + } +} diff --git a/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/PostService.java b/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/PostService.java index 77c0238ed..6ef9cacae 100644 --- a/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/PostService.java +++ b/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/PostService.java @@ -1,81 +1,62 @@ package com.example.restclient.bootrestclient.services; -import com.example.restclient.bootrestclient.exception.MyCustomRuntimeException; import com.example.restclient.bootrestclient.model.response.PostDto; import java.util.List; import java.util.Optional; import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.MediaType; +import org.springframework.http.HttpMethod; import org.springframework.stereotype.Service; -import org.springframework.web.client.RestClient; @Service public class PostService { - private final RestClient restClient; + private final HttpClientService httpClientService; - public PostService(RestClient restClient) { - this.restClient = restClient; + public PostService(HttpClientService httpClientService) { + this.httpClientService = httpClientService; } public List findAllPosts() { - return restClient - .get() - .uri("/posts") - .accept(MediaType.APPLICATION_JSON) - .retrieve() - .body(new ParameterizedTypeReference>() {}); + return httpClientService.callAndFetchResponse( + uriBuilder -> uriBuilder.path("/posts").build(), + HttpMethod.GET, + null, + new ParameterizedTypeReference>() {}); } public Optional findPostById(Long id) { - return Optional.ofNullable( - restClient - .get() - .uri(uriBuilder -> uriBuilder.path("/posts/{postId}").build(id)) - .accept(MediaType.APPLICATION_JSON) - .retrieve() - .onStatus( - HttpStatusCode::is4xxClientError, - (request, response) -> { - throw new MyCustomRuntimeException( - response.getStatusCode(), response.getHeaders()); - }) - .body(PostDto.class)); + PostDto response = + httpClientService.callAndFetchResponse( + uriBuilder -> uriBuilder.path("/posts/{postId}").build(id), + HttpMethod.GET, + null, + PostDto.class); + return Optional.ofNullable(response); } public PostDto savePost(PostDto post) { - return restClient - .post() - .uri("/posts") - .contentType(MediaType.APPLICATION_JSON) - .body(post) - .retrieve() - .body(PostDto.class); + return httpClientService.callAndFetchResponse( + uriBuilder -> uriBuilder.path("/posts").build(), + HttpMethod.POST, + post, + PostDto.class); } public Optional updatePostById(Long id, PostDto postDto) { - return Optional.ofNullable( - restClient - .put() - .uri(uriBuilder -> uriBuilder.path("/posts/{postId}").build(id)) - .contentType(MediaType.APPLICATION_JSON) - .body(postDto) - .retrieve() - .onStatus( - HttpStatusCode::is4xxClientError, - (request, response) -> { - throw new MyCustomRuntimeException( - response.getStatusCode(), response.getHeaders()); - }) - .body(PostDto.class)); + PostDto response = + httpClientService.callAndFetchResponse( + uriBuilder -> uriBuilder.path("/posts/{postId}").build(id), + HttpMethod.PUT, + postDto, + PostDto.class); + return Optional.ofNullable(response); } public String deletePostById(Long id) { - return restClient - .delete() - .uri(uriBuilder -> uriBuilder.path("/posts/{postId}").build(id)) - .retrieve() - .body(String.class); + return httpClientService.callAndFetchResponse( + uriBuilder -> uriBuilder.path("/posts/{postId}").build(id), + HttpMethod.DELETE, + null, + String.class); } } From 337be2f04e9e518bb0348f295a2333dbff04c1ec Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Tue, 23 Apr 2024 10:26:59 +0000 Subject: [PATCH 2/3] fix : use specific exception instead of Generic one --- .../bootrestclient/services/HttpClientService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/HttpClientService.java b/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/HttpClientService.java index b3232d752..6636de23e 100644 --- a/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/HttpClientService.java +++ b/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/HttpClientService.java @@ -10,13 +10,14 @@ import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.client.RestClient; import org.springframework.web.util.UriBuilder; @Service @Retryable( - retryFor = {Exception.class}, - maxAttempts = 3, + retryFor = {HttpServerErrorException.class}, + maxAttempts = 2, backoff = @Backoff(delay = 5000)) public class HttpClientService { From 5e863e9caaa63a7af532fae11c95200a48c71607 Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Tue, 23 Apr 2024 10:49:44 +0000 Subject: [PATCH 3/3] feat : add headers to final call --- .../services/HttpClientService.java | 20 +++++++++++++++++-- .../bootrestclient/services/PostService.java | 3 ++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/HttpClientService.java b/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/HttpClientService.java index 6636de23e..31668c567 100644 --- a/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/HttpClientService.java +++ b/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/HttpClientService.java @@ -2,6 +2,7 @@ import com.example.restclient.bootrestclient.exception.MyCustomClientException; import java.net.URI; +import java.util.Map; import java.util.function.Function; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; @@ -10,6 +11,7 @@ import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.client.RestClient; import org.springframework.web.util.UriBuilder; @@ -32,7 +34,15 @@ T callAndFetchResponse( HttpMethod httpMethod, @Nullable Object body, Class bodyType) { - return callServer(uriFunction, httpMethod, body, bodyType, null); + return callServer(uriFunction, httpMethod, null, body, bodyType, null); + } + + T callAndFetchResponse( + Function uriFunction, + HttpMethod httpMethod, + @Nullable Map headers, + Class bodyType) { + return callServer(uriFunction, httpMethod, headers, null, bodyType, null); } T callAndFetchResponse( @@ -40,16 +50,22 @@ T callAndFetchResponse( HttpMethod httpMethod, @Nullable Object body, ParameterizedTypeReference bodyType) { - return callServer(uriFunction, httpMethod, body, null, bodyType); + return callServer(uriFunction, httpMethod, null, body, null, bodyType); } private T callServer( Function uriFunction, HttpMethod httpMethod, + Map headers, Object body, Class bodyType, ParameterizedTypeReference typeReferenceBodyType) { RestClient.RequestBodySpec uri = restClient.method(httpMethod).uri(uriFunction); + if (!CollectionUtils.isEmpty(headers)) { + uri.headers( + httpHeader -> + headers.keySet().forEach(key -> httpHeader.add(key, headers.get(key)))); + } if (body != null) { uri.body(body); } diff --git a/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/PostService.java b/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/PostService.java index 6ef9cacae..069e645e1 100644 --- a/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/PostService.java +++ b/httpClients/boot-restclient/src/main/java/com/example/restclient/bootrestclient/services/PostService.java @@ -2,6 +2,7 @@ import com.example.restclient.bootrestclient.model.response.PostDto; import java.util.List; +import java.util.Map; import java.util.Optional; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; @@ -29,7 +30,7 @@ public Optional findPostById(Long id) { httpClientService.callAndFetchResponse( uriBuilder -> uriBuilder.path("/posts/{postId}").build(id), HttpMethod.GET, - null, + Map.of("apiKey", "123456"), PostDto.class); return Optional.ofNullable(response); }