API 에러 처리

에러가 발생할 수 있는 상황부터 정의해보자

서비스 로직 내에서 Exception 이 발생하는 경우. 로직 외 적인 부분 ( 잘못된 URL 호출, 등 ) 에서 에러가 발생하는 경우. 위 두가지 케이스에서 에러가 발생하더라도, 동일한 포맷으로 나가게끔 유도하려고 한다.

우선, 기본적으로 정의한 폼은 다음과 같습니다.

{
 "result_status": "success",
 "result_code": 200,
 "result_message": "요청에 성공하였습니다.",
 "result": { ... }
}

그리고 해당 포맷을 위한 응답 객체를 생성합니다. 해당 객체를 통해 컨트롤러에서 응답을 리턴할 때는 return new ResponseEntity<ApiResFormat>(new ApiResFormat(),HttpStatus.OK); 과 같이 리턴할 예정입니다.

package rest.api.sample.response;

import java.util.HashMap;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ApiResFormat {
    // result_status -> fail / success
    private String result_status;
    // result_code -> 200 / 4XX / 5XX
    private int result_code;
    // result_message -> NOT ACCEPTED , EXCEPTION ...
    private String result_message;
    // result -> result obj
    private HashMap<String, Object> result = new HashMap<String, Object>();

    /* 결과 예시 */
    /*
    {
        "result_status": "success",
        "result_code": 200,
        "result_message": "요청에 성공하였습니다.",
        "result": {
            ...
        }
    }
    */

    public ApiResFormat() {

    }

    public ApiResFormat(String status, int resultCode, String resultMessage, Object resultObj) {
        this.result_status = status;
        this.result_code = resultCode;
        this.result_message = resultMessage;
        result.put("data", resultObj);
    }

    public ApiResFormat(int resultCode, Object resultObj) {
        this.result_status = "success";
        this.result_code = resultCode;
        this.result_message = "요청에 성공하였습니다.";
        result.put("data", resultObj);
    }

    public ApiResFormat(int resultCode, String resultMessage, Object resultObj) {
        this.result_status = "success";
        this.result_code = resultCode;
        this.result_message = resultMessage;
        result.put("data", resultObj);
    }

    public ApiResFormat(String resultMessage, Object resultObj) {
        this.result_status = "success";
        this.result_code = 200;
        this.result_message = resultMessage;
        result.put("data", resultObj);
    }

    public ApiResFormat(Object resultObj) {
        this.result_status = "success";
        this.result_code = 200;
        this.result_message = "요청에 성공하였습니다.";
        result.put("data", resultObj);
    }
}

실패의 경우에는 실패에 맞게 바꿔 줄 생각이다. 이제 1번 케이스의 경우에는 어떻게 처리할 것인가에 대한 내용입니다.

@RestControllerAdvice, @ExceptionHandler
RestControllerAdvice  컨트롤러 단의 설정들을   있게 해주는 ControllerAdvice  확장 버전으로 응답 객체를 리턴할  있는 것이 특징이고, ExceptionHandler  Controller 단에서 발생하는 Exception을 핸들링   있도록 합니다.

따라서, 엔드포인트 컨트롤러 단에서 발생하는 Exception을 캐치하여 원하는 포맷으로 리턴하기 위해 아래와 같이 설정 했습니다.

package rest.api.sample.error;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import rest.api.sample.response.ApiResFormat;

@RestControllerAdvice
public class GlobalExceptionHandler extends RuntimeException {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiResFormat> globalException(Exception ex) {
        return new ResponseEntity<ApiResFormat>(new ApiResFormat("fail", 500, "요청에 실패하였습니다.", null),
                HttpStatus.INTERNAL_SERVER_ERROR);
    }

}

이렇게 하면 컨트롤러를 통해 로직 수행 중 발생하는 Exception 을 캐치하여 리턴해줄 수 있게 됩니다.

다음은, 로직 외 에러 상황에 대한 처리입니다.

DefaultErrorAttributes.class

DefaultErrorAttributes 는 기본적으로 스프링 부트에서 에러 발생 시 리턴하는 객체를 컨트롤 할 수 있게 해주는 클래스입니다. 기본적으로 스프링 부트는 에러 발생 시, JSON 형태로 에러를 리턴하게 되는데 그 내부의 항목들을 해당 클래스를 통해 제어할 수 있습니다.

package rest.api.sample.error;

import java.util.HashMap;
import java.util.Map;

import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;

@Component
public class RestResponseEntityExceptionHandler extends DefaultErrorAttributes {

    /**
     * @apiNote 에러 발생 시, repsonse 통일을 위한 메소드
     * @param webRequest
     * @param options
     * @return
     *         result_status = FAIL
     *         result_message = 잘못된 요청 URL 입니다.
     */
    @Override
    public Map<String, Object> getErrorAttributes(
            WebRequest webRequest, ErrorAttributeOptions options) {
        HashMap<String, Object> result = new HashMap<String, Object>();
        result.put("data", null);
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);
        errorAttributes.put("result_status", "fail");
        errorAttributes.put("result_code", Integer.parseInt(errorAttributes.get("status").toString()));
        errorAttributes.put("result_message", "허용되지 않은 접근 입니다.");
        errorAttributes.put("result", result);
        errorAttributes.remove("timestamp");
        errorAttributes.remove("status");
        errorAttributes.remove("message");
        errorAttributes.remove("error");
        errorAttributes.remove("path");
        if (errorAttributes.containsKey("trace"))
            errorAttributes.remove("trace");
        return errorAttributes;
    }

}

따라서, 위 클래스를 통해 필요한 항목은 추가하고 보여줄 필요가 없는 trace 항목이나 다른 항목들은 제외를 시켜 에러 발생시 response 형태를 최대한 통일 시키도록 작성하였습니다.

에러 발생 시 리턴 객체

restapi4


© 2024. Chiptune93 All rights reserved.