Spring boot
Error Response 작업
Subi
2023. 9. 5. 14:26
ErrorResponse
@Getter
@EqualsAndHashCode
public class ErrorResponse<T> {
private int code;
@JsonIgnore
private HttpStatus status;
private String message;
private T content;
public ErrorResponse(ErrorCode errorCode, String status, T content) {
this.code = errorCode.getCode();
this.status = errorCode.getStatus();
this.message = status;
this.content = content;
}
public ErrorResponse(ErrorCode errorCode, T content) {
this.code = errorCode.getCode();
this.status = errorCode.getStatus();
this.message = errorCode.getStatus().name();
this.content = content;
}
public ErrorResponse(ErrorCode errorCode) {
this.code = errorCode.getCode();
this.status = errorCode.getStatus();
this.message = errorCode.getStatus().name();
this.content = (T) errorCode.getContent();
}
}
ErrorResponse는 기본적으로 ResponseEntity로 감싸서 출력 되게 된다.
- 기본적인 포맷은 ResponseResult 와 동일하게 맞췄다.
- code에 대한 값은 HttpStatus에 대한 status int 값으로 출력 되게 된다.
- status에 대한 값은 ResponseEntity로 출력 될 때의 header값을 결정하게 된다.
- 또한 에러에 대한 HttpStatus 출력 값을 표기해주게 된다.
- JsonIgnore을 사용해 Response Body에서 Json 으로는 표시 되지 않는다.
- message에 대한 값은 Vaild 에서 List 형태로 들어가기 때문에 T (제네릭) 의 형태로 들어가게 된다.
- message를 따로 Parameter로 받지 않으면 기존 errorCode에 있는 Default Message 가 출력 되게 된다.
- message를 따로 Parameter로 받는 경우는 Parameter의 message가 출력 되게 된다.
ErrorCode
@Getter
@AllArgsConstructor
public enum ErrorCode {
/* 2xx (통신은 성공) : 통신은 성공했으나 데이터 상의 오류가 있습니다. */
NO_CONTENTS(204, HttpStatus.NO_CONTENT, "요청한 데이터가 존재하지 않습니다."),
NO_RESISTER(250, HttpStatus.OK,"등록되지 않은 계정입니다."), //다우 DB O , KeyClock X
NO_RESISTER_DATA(260, HttpStatus.OK,"등록되지 않은 계정입니다."), //다우 DB X , KeyClock X
/* 3xx (리다이렉션) : 요청을 완료하기 위해 추가 작업이 필요합니다. */
MULTIPLE_CHOICES(300, HttpStatus.MULTIPLE_CHOICES, "여러 가능한 응답이 있습니다."),
MOVED_PERMANENTLY(301, HttpStatus.MOVED_PERMANENTLY, "요청된 리소스에 대해 URI가 변경되었습니다."),
NOT_MODIFIED(304, HttpStatus.NOT_MODIFIED, "리소스가 변경되지 않았습니다."),
UPDATE_PASSWORD(304, HttpStatus.NOT_MODIFIED , "비밀번호 수정이 필요합니다."),
/* 4xx (클라이언트 오류) : 클라이언트의 요청에 오류가 있습니다. */
BAD_REQUEST(400, HttpStatus.BAD_REQUEST, "서버가 요청을 이해할 수 없습니다."),
MISSING_PARAMETER(400, HttpStatus.BAD_REQUEST, "파라미터가 잘못 되었습니다."),
UNAUTHORIZED(401, HttpStatus.UNAUTHORIZED, "인증이 필요합니다."),
LOGIN_FAIL(401, HttpStatus.UNAUTHORIZED, "로그인 정보가 올바르지 않습니다."),
FORBIDDEN(403, HttpStatus.FORBIDDEN, "서버가 요청을 이해하였으나 그 요청을 수행하는 것을 거부합니다."),
NOT_FOUND(404, HttpStatus.NOT_FOUND, "서버가 요청한 페이지를 찾을 수 없습니다."),
METHOD_NOT_ALLOWED(405, HttpStatus.METHOD_NOT_ALLOWED, "지원하지 않는 메서드입니다."),
CONFLICT(409, HttpStatus.CONFLICT, "중복된 리소스가 존재합니다."),
TOO_MANY_REQUESTS(429, HttpStatus.TOO_MANY_REQUESTS, "사용자가 일정 시간 동안 너무 많은 요청을 보냈습니다."),
/* 5xx (서버 오류) : 서버가 유효한 요청을 충족시키지 못했습니다.*/
INTERNAL_SERVER_ERROR(500, HttpStatus.INTERNAL_SERVER_ERROR, "서버에 오류가 발생하여 요청을 완료할 수 없습니다."),
NOT_IMPLEMENTED(501, HttpStatus.NOT_IMPLEMENTED, "서버에 요청한 기능이 구현되어 있지 않습니다."),
BAD_GATEWAY(502, HttpStatus.BAD_GATEWAY, "서버가 게이트웨이나 프록시 역할을 하면서 잘못된 응답을 받았습니다."),
SERVICE_UNAVAILABLE(503, HttpStatus.SERVICE_UNAVAILABLE, "서버가 일시적으로 사용할 수 없습니다."),
GATEWAY_TIMEOUT(504, HttpStatus.GATEWAY_TIMEOUT, "게이트웨이나 프록시 서버에서 요청을 기다리는 동안 시간이 초과되었습니다."),
DATA_ACCESS(580, HttpStatus.INTERNAL_SERVER_ERROR, "사용자 설정 에러");
private int code;
private HttpStatus status;
private String content;
}
1. 데이터가 존재하지 않을 때
- NoContentsException
- ErrorCode : ErrorCode.No_CONTENTS
@Getter
public class NoContentsException extends RuntimeException{
private ErrorCode errorCode;
public NoContentsException(String message){
super(message);
this.errorCode = ErrorCode.NO_CONTENTS;
}
public NoContentsException(){
this.errorCode = ErrorCode.NO_CONTENTS;
}
}
- Exception Handler
/**
* 204 : 요청한 데이터가 존재하지 않을시
*/
@ExceptionHandler(NoContentsException.class)
protected ResponseResult<Object> noContentsException(NoContentsException exception){
ErrorResponse errorResponse = new ErrorResponse(exception.getErrorCode());
if(StringUtils.hasText(exception.getMessage())){
errorResponse = new ErrorResponse(exception.getErrorCode(), exception.getMessage());
}
return ResponseResult.response(errorResponse.getContent(),ResponseStatus.NO_CONTENTS);
}
- Response
- Header : 200
{
"code": 204,
"message": "NO_CONTENTS",
"content": "요청한 데이터가 존재하지 않습니다."
}
2. 다우 DB 에는 존재하지만 KeyClock에는 등록되지 않은 계정
- NoResisterException
- ErrorCode : ErrorCode.NO_RESISTER
@Getter
public class NoResisterException extends RuntimeException{
private ErrorCode errorCode;
public NoResisterException(){
this.errorCode = ErrorCode.NO_RESISTER;
}
}
- Exception Handler
/**
* 250 : 다우 O , KeyClock X 등록되지 않은 계정
*/
@ExceptionHandler(NoResisterException.class)
protected ResponseEntity<Object> noResisterException(NoResisterException exception){
ErrorResponse errorResponse = new ErrorResponse(exception.getErrorCode(),"NO_RESISTER", exception.getErrorCode().getContent());
return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
- Response
- Header : 200
{
"code": 250,
"message": "NO_RESISTER",
"content": "등록되지 않은 계정입니다."
}
3. 다우 DB 에는 존재하지않고 KeyClock에는 등록되지 않은 계정
- NoResisterDataException
- ErrorCode : ErrorCode.NO_RESISTER_DATA
@Getter
public class NoResisterDataException extends RuntimeException{
private ErrorCode errorCode;
public NoResisterDataException(){
this.errorCode = ErrorCode.NO_RESISTER_DATA;
}
}
- Exception Handler
/**
* 260 : 다우 X , KeyClock X 등록되지 않은 계정
*/
@ExceptionHandler(NoResisterDataException.class)
protected ResponseEntity<Object> noResisterDataException(NoResisterDataException exception){
ErrorResponse errorResponse = new ErrorResponse(exception.getErrorCode(),"NO_RESISTER", exception.getErrorCode().getContent());
return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
- Response
- Header : 200
{
"code": 260,
"message": "NO_RESISTER",
"content": "등록되지 않은 계정입니다."
}
4. 비밀번호 수정이 필요한 경우
- UpdatePasswordException
- ErrorCode : ErrorCode.UPDATE_PASSWORD
@Getter
public class UpdatePasswordException extends RuntimeException{
private ErrorCode errorCode;
public UpdatePasswordException(String message){
super(message);
this.errorCode = ErrorCode.UPDATE_PASSWORD;
}
}
- Exception Handler
/**
* 304 : 비밀번호 수정 필요
*/
@ExceptionHandler(UpdatePasswordException.class)
protected ResponseEntity<Object> updatePasswordException(UpdatePasswordException exception){
ErrorResponse errorResponse = new ErrorResponse(exception.getErrorCode(), exception.getMessage());
return new ResponseEntity<>(errorResponse, HttpStatus.OK);
}
- Response
- Header : 200
{
"code": 304,
"message": "UPDATE_PASSWORD",
"content": "토큰 정보"
}
5. 올바르지 않은 Parameter
- MissingServletRequestParameterException
- ErrorCode : ErrorCode.MISSING_PARAMETER
- Exception Handler
/**
* 400 : Parameter Error
*/
@Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
ErrorResponse errorResponse = new ErrorResponse(ErrorCode.MISSING_PARAMETER);
return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
- Response
- Header : 400
{
"code": 400,
"message": "MISSING_PARAMETER",
"content": "토큰 정보"
}
6. 로그인 정보가 다른 경우 (로그인 실패)
- LoginFailException
- ErrorCode : ErrorCode.LOGIN_FAIL
@Getter
public class LoginFailException extends RuntimeException{
private ErrorCode errorCode;
public LoginFailException(){
this.errorCode = ErrorCode.LOGIN_FAIL;
}
}
- Exception Handler
/**
* 401 : 로그인 정보가 다른 경우 (로그인 실패)
*/
@ExceptionHandler(LoginFailException.class)
protected ResponseEntity<Object> loginFailException(LoginFailException exception){
ErrorResponse errorResponse = new ErrorResponse(ErrorCode.LOGIN_FAIL);
return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
- Response
- Header : 401
{
"code": 401,
"message": "UNAUTHORIZED",
"content": "로그인 정보가 올바르지 않습니다."
}
7. 서버가 요청한 자원을 찾지 못한 경우
- NoHandlerFoundException
- ErrorCode : ErrorCode.NOT_FOUND
- Exception Handler
/**
* 404 : 서버가 요청한 자원을 찾지 못하는 경우
*/
@Override
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
ErrorResponse errorResponse = new ErrorResponse(ErrorCode.NOT_FOUND);
return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
- Response
- Header : 404
{
"code": 404,
"message": "NOT_FOUND",
"content": "서버가 요청한 페이지를 찾을 수 없습니다."
}
8. 지원하지 않는 HTTP 메서드로 요청
- HttpRequestMethodNotSupportedException
- ErrorCode : ErrorCode.METHOD_NOT_ALLOWED
- Exception Handler
/**
* 405 : 지원하지 않는 HTTP 메서드로 요청
*/
@Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
StringBuilder builder = new StringBuilder();
builder.append(ex.getMethod());
builder.append(" 는 지원하지 않는 메서드입니다. ");
Set<HttpMethod> supportedHttpMethods = ex.getSupportedHttpMethods();
if (supportedHttpMethods != null) {
supportedHttpMethods.forEach(t -> builder.append(t).append(" "));
}
builder.append("으로 요청해주시기 바랍니다");
ErrorResponse errorResponse = new ErrorResponse(ErrorCode.METHOD_NOT_ALLOWED, builder.toString());
return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
- Response
- Header : 405
{
"code": 405,
"message": "METHOD_NOT_ALLOWED",
"content": "POST 는 지원하지 않는 메서드입니다. GET 으로 요청해주시기 바랍니다"
}
9. 중복에 대한 에러
- DuplicateException
- ErrorCode : ErrorCode.CONFICT
@Getter
public class DuplicateException extends RuntimeException{
private ErrorCode errorCode;
public DuplicateException(String message){
super(message);
this.errorCode = ErrorCode.CONFLICT;
}
}
- Exception Handler
/**
* 409 : 중복에 대한 에러
*/
@ExceptionHandler(DuplicateException.class)
protected ResponseEntity<ErrorResponse> duplicateException(DuplicateException exception){
ErrorResponse errorResponse = new ErrorResponse(exception.getErrorCode(),exception.getMessage());
return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
- Response
- Header : 409
{
"code": 409,
"message": "CONFLICT",
"content": "TemplateName 이 중복되었습니다."
}
10. 유효성 검사 실패
- ValidationResponse
- ErrorCode : ErrorCode.VALIDATION
- 유효성 검사에 실패한 객체를 List 에 담아 Content로 출력
@Getter
public class ValidationResponse {
private String column;
private ValidationType type;
private String message;
public ValidationResponse(String column, String type, String message) {
this.column = column;
this.type = getType(type);
this.message = message;
}
private ValidationType getType(String type) {
Map<String, ValidationType> typeMap = new HashMap<>();
typeMap.put("NotNull", ValidationType.EMPTY);
typeMap.put("NotBlank", ValidationType.EMPTY);
typeMap.put("Pattern", ValidationType.PATTERN);
typeMap.put("EnumValid" , ValidationType.ENUM_VALID);
return typeMap.getOrDefault(type, ValidationType.EMPTY);
}
}
public enum ValidationType {
EMPTY , PATTERN , ENUM_VALID
}
- Exception Handler
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
BindingResult bindingResult = ex.getBindingResult();
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
List<ValidationResponse> errorList = new ArrayList<>();
for (FieldError fieldError : fieldErrors) {
ValidationResponse validationResponse = new ValidationResponse(fieldError.getField(), fieldError.getCode(),fieldError.getDefaultMessage());
errorList.add(validationResponse);
}
ErrorResponse errorResponse = new ErrorResponse(ErrorCode.VALIDATION,"VALIDATION_ERROR", errorList);
return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
- Response
- Header : 400
{
"code": 410,
"message": "BAD_REQUEST",
"content": [
{
"column": "sendLocation",
"type": "ENUM_VALID",
"message": "유효하지 않은 값입니다 (유효한 값 :MEMBERSHIP, BILLING, CRUELLA, CS, OTHER)"
},
{
"column": "dmCodeName",
"type": "EMPTY",
"message": "공백일 수 없습니다"
},
{
"column": "dmCode",
"type": "PATTERN",
"message": "올바르지 않은 정규식 입니다."
}
]
}
11. 특정하지 않은 모든 에러
- Exception
- ErrorCode : ErrorCode.INTERNAL_SERVER_ERROR
- Exception Handler
- Log 로 System Error 확인
- 특정하지 않은 모든 에러 Handler
/**
* 500 : 특정하지 않은 모든 에러
*/
@ExceptionHandler(value = Exception.class)
protected ResponseEntity<Object> handleAll(Exception exception) {
log.info("exception.getMessage : "+ exception.getMessage());
log.info("exception.getLocalizedMessage : "+ exception.getLocalizedMessage());
ErrorResponse errorResponse = new ErrorResponse(ErrorCode.INTERNAL_SERVER_ERROR);
return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
- Response
- Header : 500
{
"code": 500,
"message": "INTERNAL_SERVER_ERROR",
"content": "서버에 오류가 발생하여 요청을 완료할 수 없습니다."
}
12. SQL에 대한 에러
- DataAccessException
- ErrorCode : ErrorCode.DATA_ACCESS
- Exception Handler
/**
* 550 : SQL 에 대한 에러
*/
@ExceptionHandler(DataAccessException.class)
protected ResponseEntity<Object> dataAccessException(){
ErrorResponse errorResponse = new ErrorResponse(ErrorCode.DATA_ACCESS);
return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
- Response
- Header : 500
{
"code": 550,
"message": "INTERNAL_SERVER_ERROR",
"content": "SQL를 실행 하는 중 에러가 발생했습니다."
}
13. 사용자 설정 에러
- IllegalArgumentException
- ErrorCode : ErrorCode.CUSTOM_ERROR
- Exception Handler
/**
* 580 : 사용자 설정 에러
*/
@ExceptionHandler(value = IllegalArgumentException.class)
protected ResponseEntity<Object> handleIllegalArgumentException(IllegalArgumentException exception) {
ErrorResponse errorResponse = new ErrorResponse(ErrorCode.CUSTOM_ERROR, exception.getMessage());
return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
- Response
- Header : 500
{
"code": 580,
"message": "INTERNAL_SERVER_ERROR",
"content": "사용자 설정 에러"
}