@FeignClient 요청 및 에러 핸들링
기본 세팅 이전 포스팅 참고
https://programmingjun.tistory.com/96
@FeignClient 기본 세팅
프로젝트에서 외부 api에 요청을 보내야할 경우들이 있어 FeinClient를 사용해보았다. 이 과정중에 발생한 에러를 해결하며 공부하게된 내용들을 정리해본다. 의존성 관리해당 의존성은 Springboot 3.
programmingjun.tistory.com
전역, 부분적으로 request를 핸들링하기 위해서 Configuragion을 활용할 수 있다.
예를 들어 전역적인 요청에 header를 세팅한다거나 특정 외부 api를 호출할 때 동일하게 header를 세팅해야 할 경우
전역 RequestInterceptor 설정
@Configuration을 통해 전역의 요청을 설정할 수 있다.
@Configuration
public class GlobalFeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> requestTemplate.header("global-config-header", "GlobalFeignConfig");
}
}
응답값
부분적 RequestInterceptor 설정
만일 특정 외부 api에만 요청 설정을 해야 할 경우 아래와 같이 사용할 수 있다.
@Configuration 을 사용하지 않은 이유는 전역으로 설정할 것이 아닌 특정 환경에서만 활용 가능하도록 하기 위함이다
public class FeignClientConfig {
Map<String, Collection<String>> headers = new HashMap<>();
@Bean
public RequestInterceptor requestInterceptor() {
headers.put("global-config-header", List.of("GlobalFeignConfig"));
headers.put("custom-config-header1", List.of("FeignClientConfig1"));
headers.put("custom-config-header2", List.of("FeignClientConfig2"));
return requestTemplate -> requestTemplate.headers(headers);
}
}
@FeignClient 어노테이션에서 configuration 옵션에 설정을 지정하면 해당 RequestInterceptor를 적용시킬 수 있다.
기존 인터셉터의 설정이 아닌 옵션에 부여한 인터셉터가 적용된다.
@FeignClient(name = "example", url = "http://localhost:8282", configuration = FeignClientConfig.class)
public interface FeignClientService {
@GetMapping(value = "/test")
Result getInfo(
@RequestHeader(value = "Authorization", required = false) String token,
@RequestParam(name = "name", required = false)String name,
@RequestParam(name = "age", required = false) Integer age,
@RequestParam(name = "status" , required = false)Integer status);
}
결과 값
요청 핸들링 결론 : 전역적으로 @Configuration를 붙여 전역적으로 핸들링하고 , 부분적으로 요청 핸들링 해야 하는 경우 @Configuration이 붙지 않은 설정을 적용한다.
에러 핸들링
커스텀 에러 세팅
에러 코드
@AllArgsConstructor
@Builder
@Getter
public class ErrorCode {
private final HttpStatus httpStatus;
private final String message;
}
커스텀 익셉션
@AllArgsConstructor
@Getter
public class CustomException extends RuntimeException{
private ErrorCode errorCode;
@Override
public String getMessage() {
return errorCode.getMessage();
}
}
에러 반환 계층
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
public class ErrorResponse {
private int status;
private String code;
private String message;
// 커스텀에서 사용시
public static ResponseEntity<ErrorResponse> ofResponse(ErrorCode e) {
return ResponseEntity
.status(e.getHttpStatus())
.body(ErrorResponse.builder()
.status(e.getHttpStatus().value())
.code(e.getHttpStatus().name())
.message(e.getMessage())
.build()
);
}
// 이외 익셉션
...
}
익셉션 핸들러
@RestControllerAdvice
@Order(1)
@Slf4j
public class ControllerAdvice {
@ExceptionHandler(CustomException.class)
public ResponseEntity<ErrorResponse> handleCustomException(CustomException e) {
return ErrorResponse.ofResponse(e.getErrorCode());
}
...
}
@FeignClient 설정 추가
@Configuration
public class GlobalFeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> requestTemplate.header("global-config-header", "GlobalFeignConfig");
}
// 애러 디코더 Bean 설정
@Bean
public ErrorDecoder errorDecoder() {
return new CustomFeignErrorDecoder();
}
}
ErrorDecoder 구현
외부 api 호출 시 애러가 발생하면 디코딩해주는 구현체 애러 핸들링을 어떻게 할지는 커스텀하는 방식에 따라 다를 것 같다.
@Slf4j
public class CustomFeignErrorDecoder implements ErrorDecoder {
@SneakyThrows
@Override
public Exception decode(String s, Response response) {
//HttpStatus
HttpStatus responseStatus = HttpStatus.valueOf(response.status());
//메시지 파싱
String requestUrl = response.request().url();
Response.Body body = response.body();
JSONParser jp = new JSONParser();
JSONObject parseBody = (JSONObject) jp.parse(new InputStreamReader(body.asInputStream()));
String message = "[URL] : " + requestUrl + " [Body] :" + parseBody.toString();
if (responseStatus.is5xxServerError()) {
//5xx 애러 핸들링
return new CustomException(ErrorCode.builder().httpStatus(responseStatus).message(message).build());
} else if (responseStatus.is4xxClientError()) {
//4xx 애러 핸들링
return new CustomException(ErrorCode.builder().httpStatus(responseStatus).message(message).build());
} else {
return new Exception("Generic exception");
}
}
}
결과
파라미터로 status를 받아 그 상태를 그대로 반환하게 설정한 외부 api 애플리케이션에서 404를 반환하였고 다시 받아서 애러 핸들링 한 모습니다.
404 발생 시