Skip to content

Commit

Permalink
feat : streamlining client request handling (#1202)
Browse files Browse the repository at this point in the history
* feat : modularize httpClient Call

* fix : use specific exception instead of Generic one

* feat : add headers to final call
  • Loading branch information
rajadilipkolli authored Apr 23, 2024
1 parent cbf0864 commit 22a3e1a
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 55 deletions.
8 changes: 8 additions & 0 deletions httpClients/boot-restclient/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.example.restclient.bootrestclient.services;

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;
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.util.CollectionUtils;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestClient;
import org.springframework.web.util.UriBuilder;

@Service
@Retryable(
retryFor = {HttpServerErrorException.class},
maxAttempts = 2,
backoff = @Backoff(delay = 5000))
public class HttpClientService {

private final RestClient restClient;

public HttpClientService(RestClient restClient) {
this.restClient = restClient;
}

<T> T callAndFetchResponse(
Function<UriBuilder, URI> uriFunction,
HttpMethod httpMethod,
@Nullable Object body,
Class<T> bodyType) {
return callServer(uriFunction, httpMethod, null, body, bodyType, null);
}

<T> T callAndFetchResponse(
Function<UriBuilder, URI> uriFunction,
HttpMethod httpMethod,
@Nullable Map<String, String> headers,
Class<T> bodyType) {
return callServer(uriFunction, httpMethod, headers, null, bodyType, null);
}

<T> T callAndFetchResponse(
Function<UriBuilder, URI> uriFunction,
HttpMethod httpMethod,
@Nullable Object body,
ParameterizedTypeReference<T> bodyType) {
return callServer(uriFunction, httpMethod, null, body, null, bodyType);
}

private <T> T callServer(
Function<UriBuilder, URI> uriFunction,
HttpMethod httpMethod,
Map<String, String> headers,
Object body,
Class<T> bodyType,
ParameterizedTypeReference<T> 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);
}
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);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,81 +1,63 @@
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.Map;
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<PostDto> findAllPosts() {
return restClient
.get()
.uri("/posts")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(new ParameterizedTypeReference<List<PostDto>>() {});
return httpClientService.callAndFetchResponse(
uriBuilder -> uriBuilder.path("/posts").build(),
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<PostDto>>() {});
}

public Optional<PostDto> 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,
Map.of("apiKey", "123456"),
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<PostDto> 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);
}
}

0 comments on commit 22a3e1a

Please sign in to comment.