Spring

@FeignClient 요청 및 에러 핸들링

쭈녁 2024. 5. 22. 21:35

 

기본 세팅 이전 포스팅 참고

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 발생 시

 

400 발생 시