springboot resttemplate config 와 restClient 생성

2024. 1. 19. 15:51·개발/java

 

동기 통신이다. 우선 resttemplate 를 써보자

 

환경은 다음과 같다.

  • springboot 2.7.18
  • jdk 11.0.21

dependency를 추가한다.

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

 

공통화를 위해 우선 interceptor를 만들어보자.

ClientHttpRequestInterceptor를 구현하는 HttpClientRequestInterceptor 클래스를 생성하여 req, res에 대해 로깅을 준비한다. 

@Slf4j
public class HttpClientRequestInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
	    throws IOException {
	logReq(request.getURI(), body);
	ClientHttpResponse response = execution.execute(request, body);
	logRes(request.getURI(), response);
	return response;
    }

    private void logReq(URI requestUri, byte[] body) throws IOException {
	if (log.isInfoEnabled()) {
	    log.info("- URI : {}", requestUri);
	    log.info("- request body : {}", new String(body, StandardCharsets.UTF_8));
	}
    }

    private void logRes(URI requestUri, ClientHttpResponse response) throws IOException {
	if (log.isInfoEnabled()) {
	    log.info("- URI : {}", requestUri);
	    log.info("- Response statuscode : {} ", response.getStatusCode());
	    log.info("- Response body : {} ", IOUtils.toString(response.getBody(), StandardCharsets.UTF_8));
	}
    }

}

 

이제 exception 처리 용 ResponseErrorHanlder를 구현하는 RestTemplateErrorHandler 클래스를 생성해보자.

4xx, 5xx 발생시 Exception throw를 한다. 

@Slf4j
public class RestTemplateErrorHandler implements ResponseErrorHandler {

    @Override
    public boolean hasError(ClientHttpResponse httpResponse) throws IOException {
	return httpResponse.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR //4xx
		|| httpResponse.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR; //5xx
    }

    @Override
    public void handleError(ClientHttpResponse httpResponse) throws IOException {

	String errorStr = IOUtils.toString(httpResponse.getBody(), StandardCharsets.UTF_8);
	log.debug("handleError statusCode: {}", httpResponse.getStatusCode().value());
	log.debug("handleError errorStr: {}", errorStr);
	if (httpResponse.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR
		|| httpResponse.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR) {
        //FIXME 각각에 대한 custom exception설정이 필요한 경우 이 부분을 수정한다.
	    throw new RuntimeException(errorStr); 
	}
    }

}

 

이제 resttemplate configuration을 위한 RestTemplateConfig 클래스를 생성한다.

HttpClient와 HttpComponentClientHttpRequestFactory Bean을 등록하고 global한 timeout 을 설정한다. 

http://localhost:8080/로 자기 자신의 api call 을 위한 SelfRestTemplate bean 함께 생성한다.  

@Configuration
public class RestTemplateConfig {

    @Value("${target.self:http://localhost:8080}")
    private String targetSelf;
    
    @Bean
    HttpClient httpClient() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
	TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
	SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, acceptingTrustStrategy).build();
	SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
	return HttpClientBuilder.create().setMaxConnTotal(200).setMaxConnPerRoute(100) // IP:포트별 최대 커넥션 수
		.setConnectionTimeToLive(30, TimeUnit.SECONDS) // keep-alive
		.setSSLSocketFactory(csf).build();
    }

    @Bean
    HttpComponentsClientHttpRequestFactory factory(HttpClient httpClient) {
	HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(
		httpClient);
	httpComponentsClientHttpRequestFactory
		.setConnectionRequestTimeout(3); // 3s
	httpComponentsClientHttpRequestFactory.setConnectTimeout(3000); // 3s
	httpComponentsClientHttpRequestFactory.setReadTimeout(30000); // 30s
	return httpComponentsClientHttpRequestFactory;
    }

    @Bean("selfRestTemplate")
    RestTemplate selfRestTemplate(RestTemplateBuilder restTemplateBuilder,
	    HttpComponentsClientHttpRequestFactory factory) {
	return restTemplateBuilder.rootUri(targetSelf)
		.additionalMessageConverters(new StringHttpMessageConverter(StandardCharsets.UTF_8),
			new MappingJackson2HttpMessageConverter())
		.setConnectTimeout(Duration.ofSeconds(3)).setReadTimeout(Duration.ofSeconds(30))
		.interceptors(new HttpClientRequestInterceptor()).errorHandler(new RestTemplateErrorHandler())
		.customizers(
			restTemplate -> restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(factory)))
		.defaultHeader("organization", "supermoon")
		.build();
    }

}

 

RestClient를 만든다. JSON 포멧으로 통신하도록 contentType을 지정하고, HttpEntity 를 생성하여 restTemplate의 exchange 메소드에 전달한다.

@Component
public class SelfRestClient {

    private final RestTemplate selfRestTemplate;

    public SelfRestClient(@Qualifier("selfRestTemplate") RestTemplate selfRestTemplate) {
	this.selfRestTemplate = selfRestTemplate;
    }

    @SuppressWarnings("unchecked")
    public <T> T get(String uri, Object obj, Class<?> clazz) {
	HttpEntity<?> httpEntity = makeHttpHeader(HttpMethod.GET, obj);
	return (T) selfRestTemplate.exchange(uri, HttpMethod.GET, httpEntity, clazz);
    }

    @SuppressWarnings("unchecked")
    public <T> T post(String uri, Object obj, Class<?> clazz) {
	HttpEntity<?> httpEntity = makeHttpHeader(HttpMethod.POST, obj);
	return (T) selfRestTemplate.exchange(uri, HttpMethod.POST, httpEntity, clazz);
    }

    @SuppressWarnings("deprecation")
    private HttpHeaders makeHeader() {
	HttpHeaders httpHeaders = new HttpHeaders();
	httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
	httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
	return httpHeaders;
    }

    private HttpEntity<?> makeHttpHeader(HttpMethod httpMethod, Object param) {
	HttpHeaders httpHeaders = makeHeader();
	return httpMethod == HttpMethod.GET ? new HttpEntity<>(httpHeaders) : new HttpEntity<>(param, httpHeaders);
    }
}

 

이제 테스트를 해보자. 

테스트 호출용, 응답용 API를 만든다.

 

@RestController
@RequestMapping("/api/rest")
public class RestTemplateTestController {

    private final SelfRestClient selfRestClient;
    
    public RestTemplateTestController(SelfRestClient selfRestClient) {
	this.selfRestClient = selfRestClient;
    }

    @GetMapping("/return-get-value/{number}")
    public ResponseEntity<EventVO> returnGetValue(@PathVariable("number") int number) {

	EventVO event = new EventVO();
	event.setName("GetTest");
	event.setNumber(number);
	return ResponseEntity.ok(event);
    }

    @PostMapping("/return-post-value")
    public ResponseEntity<EventVO> returnPostValue(@RequestBody EventVO event) {

	event.setWage(1000000);
	return ResponseEntity.ok(event);
    }

    @PostMapping("/call-get-value")
    public ResponseEntity<EventVO> callGetValue(@RequestBody EventVO event) {

	return selfRestClient.get("/api/rest/return-get-value/11", null, EventVO.class);
    }

    @PostMapping("/call-post-value")
    public ResponseEntity<EventVO> callPostValue(@RequestBody EventVO event) {

	return selfRestClient.post("/api/rest/return-post-value", event, EventVO.class);
    }

}

 

swagger로 호출해본다.

먼저 post요청이다.

정상 응답을 받았다.

다음 get요청 또한 정상응답을 받는다.

콘솔에서 인터셉터가 잘 찍혔는지 확인해보니 잘 찍혔다.

마지막으로 없는 주소를 호출해본다. 스웨거 응답에는 익셉션이 찍혔다.

콘솔창에서도 확인해본다. 에러핸들러에 설정한 내용이 잘 찍혔다.

 

Git: sync/rest at dev · FullMooney/sync (github.com)

 

728x90

'개발 > java' 카테고리의 다른 글

@Aspect로 공통header 처리  (2) 2024.02.19
springboot threadLocal 테스트  (1) 2024.01.22
springboot rabbitmq config와 DLQ 예제  (0) 2024.01.18
springboot + redis @Cacheable 사용  (3) 2024.01.11
springboot redis client 만들기  (1) 2024.01.08
'개발/java' 카테고리의 다른 글
  • @Aspect로 공통header 처리
  • springboot threadLocal 테스트
  • springboot rabbitmq config와 DLQ 예제
  • springboot + redis @Cacheable 사용
yunapapa
yunapapa
working on the cloud
    250x250
  • yunapapa
    supermoon
    yunapapa
  • 전체
    오늘
    어제
    • 분류 전체보기 (94)
      • 개발 (20)
        • java (17)
        • web (2)
        • MSX (1)
        • Go (0)
      • CloudNative (50)
        • App Definition & Developeme.. (17)
        • Orchestration & Management (4)
        • Runtime (3)
        • Provisioning (7)
        • Observability & Analysis (14)
        • event review (5)
      • AWS (7)
      • 환경관련 (17)
      • 취미생활 (0)
        • 맛집 (0)
        • 게임 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • CNCF Past Events
    • Kubernetes Korea Group
  • 공지사항

  • 인기 글

  • 태그

    istio
    devops
    springboot
    gitlab
    Pinpoint
    Java
    dop-c02
    k8s
    helm
    OpenShift
    kubernetes
    오블완
    티스토리챌린지
    APM
    AWS
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
yunapapa
springboot resttemplate config 와 restClient 생성
상단으로

티스토리툴바