-
Notifications
You must be signed in to change notification settings - Fork 8
ProblemDetail
클라이언트 요청에서 에러가 발생했을 때, 우리는 에러가 발생했다고 알려주기 위해 오류 메시지를 보내야 한다. 하지만 이 오류 메시지에는 어떤 내용을 담아야 할까?
이에 대한 규약을 정해준 것이 바로 RFC 7807이다. RFC 7807은 API 에러 응답에 대한 규약을 정의한 문서이고 현재 Standard Track으로 표준화되었다.
RFC 7807에서 제시한 오류 메시지 구조는 총 5개의 필드로 구성되어 있다.
- type : 에러를 분류하기 위한 URI 식별자
- title : 에러에 대한 간략한 설명
- status : HTTP response code
- detail : 에러에 대한 자세한 설명
- instance : 에러가 발생한 URI
ProblemDetail 클래스는 이 RFC 7807 표준을 따라 Spring 에서 새롭게 만든 클래스이다. 스프링 프레임워크 6.0.x 버전부터 사용을 할 수 있고, RFC 7807 Problem Detail for API 문서 이름의 응답 객체 명세 이름을 그대로 사용하였다.
ProblemDetail 클래스는 기본적으로 아래와 같은 필드를 가지고 있다.
public class ProblemDetail {
/**
* 문제 유형을 식별하는 URI 참조.
* 이 URI가 참조되면 문제 유형에 대한 문서를 제공해야 한다.
* 값이 없는 경우 "about:blank"으로 가정된다.
*/
private static final URI BLANK_TYPE = URI.create("about:blank");
private URI type;
/**
* 문제 유형에 대한 사람이 읽을 수 있는 간단한 요약
*/
@Nullable
private String title;
/**
* 이 문제의 응답 Http status 코드
*/
private int status;
/**
* 문제의 발생에 대한 사람이 읽을 수 있는 구체적인 설명
*/
@Nullable
private String detail;
/**
* 문제가 발생한 URI
*/
@Nullable
private URI instance;
}
더 자세한 내용은 해당 링크를 참조하길 바란다.
아래의 코드는 ProblemDetail을 사용하지 않고 예외처리를 진행한 코드들이다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler
public ResponseEntity<String> handleCodeZapException(CodeZapException codeZapException) {
return new ResponseEntity<>(codeZapException.getMessage(), codeZapException.getStatus());
}
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler
public ResponseEntity<ExceptionResponse> handleCodeZapException(CodeZapException codeZapException) {
return ResponseEntity.status(codeZapException.getStatus())
.body(codeZapException.getMessage());
}
}
RFC 7807 표준을 따르지도 못했고, 만약 따른다면 굉장히 긴 코드가 될 것이다.
위 코드들을 ProblemDetail을 사용하여 다시 작성해보자.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler
public ResponseEntity<ProblemDetail> handleCodeZapException(CodeZapException codeZapException) {
return ResponseEntity.status(codeZapException.getHttpStatusCode())
.body(ProblemDetail.forStatusAndDetail(
codeZapException.getHttpStatusCode(),
codeZapException.getMessage())
);
}
}
굉장히 간단하게 RFC 7807 표준을 따를 수 있게 되었다.
해당 코드를 사용하여 에러를 발생시키면 아래와 같은 응답이 오게 된다.
{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"detail": "식별자 1에 해당하는 템플릿이 존재하지 않습니다.",
"instance": "/templates/1"
}
여기서 궁금한 점이 발생하게 된다.
나는 다음과 같이 status
와 detail
만 전달해주었다.
return ResponseEntity.status(codeZapException.getHttpStatusCode())
.body(ProblemDetail.forStatusAndDetail(
codeZapException.getHttpStatusCode(),
codeZapException.getMessage())
);
하지만 어떻게 title에는 NOT FOUND
가 들어가고, instance에는 해당 에러가 발생한 URI인 /templates/1
이 들어가게 되는 것일까?
결론부터 이야기하면 instance의 경우는 정확한 이유를 찾지 못했다. 추후에 알게 된다면 해당 내용을 추가하도록 하겠다.
title의 경우에는 ProblemDetail의 내부 구현에 정답이 있었다. 다음은 ProblemDetail 내부에서 구현되어 있는 코드 중 우리의 코드에서 사용되는 부분들이다.
public class ProblemDetail {
protected ProblemDetail(int rawStatusCode) {
this.type = BLANK_TYPE;
this.status = rawStatusCode;
}
@Nullable
public String getTitle() {
if (this.title == null) {
HttpStatus httpStatus = HttpStatus.resolve(this.status);
if (httpStatus != null) {
return httpStatus.getReasonPhrase();
}
}
return this.title;
}
public static ProblemDetail forStatus(int status) {
return new ProblemDetail(status);
}
public static ProblemDetail forStatusAndDetail(HttpStatusCode status, @Nullable String detail) {
Assert.notNull(status, "HttpStatusCode is required");
ProblemDetail problemDetail = forStatus(status.value());
problemDetail.setDetail(detail);
return problemDetail;
}
}
위와 같이 우리는 status
와 detail
정보만 전달해줬지만 getTitle()
을 할 때 만약 title
이 null
이라면 전달 받은 HttpStatus
에서 정보를 가져오는 것을 알 수 있다.
이와 같이 우리는 스프링에서 제공해주는 ProblemDetail
클래스를 이용하여 손쉽게 에러 메시지를 전달해줄 수 있게 되었다.
- 백엔드 코드 컨벤션
- 백엔드 기술 스택 및 선정 이유
- 각종 인스턴스 설정 파일 및 구성 위치 가이드
- ERD (24.09.27)
- 백엔드 CI CD 동작 프로세스
- 로컬 DB 환경 설정
- 백엔드 로깅 전략
- 백엔드 로그 모니터링 구성도
- 스프링 메트릭 모니터링 구성도
- Flyway 로 스키마 관리
- 코드잽 서버 구성도
- Git Submodule 사용 메뉴얼
- 프론트엔드 코드 컨벤션
- 프론트엔드 기술 스택 및 선정 이유
- 프론트엔드 서비스 타겟 환경 및 브라우저 지원 범위 선정
- 프론트엔드 모니터링 및 디버깅 환경 구축
- 프론트엔드 테스트 목록
- 프론트엔드 라이브러리 기술 검토
- 프론트엔드 개발서버, 운영서버 빌드 및 배포 환경 구분
- 목표했던 타겟 환경과 디바이스에서 서비스 핵심 기능 동작 확인
- 프론트엔드 접근성 개선 보고서
- EC2 로그 확인 방법
- VSCode를 통한 EC2 인스턴스 SSH 연결 방법
- 터미널을 통한 EC2 인스턴스 SSH 연결 방법
- NGINX 설정 파일 접근 및 적용 방법
- DB 접속 및 백업 방법
- [QA] 배포 전 체크리스트
- CI 파이프라인 구축
- CD 파이프라인 구축
- 백엔드 CI CD 트러블슈팅
- Lombok Annotation Processor 의존성을 추가한 이유
- 2차 스프린트 기준 ERD
- DTO 검증하기
- ProblemDetail
- Fork된 레포지토리 PR에서 CI Secrets 접근 문제 해결
- AWS CloudWatch 모니터링
- 스프링 메트릭 모니터링 구축 방법
- 로깅과 Logback에 대해 알아보아요.
- 백엔드 CD 파이프라인 Ver.2
- 요청, 응답 로그에 correlationId 를 추가하자!
- 3차 스프린트 기준 ERD
- 더미데이터 생성하고 실행하기
- 쿼리 성능 개선 결과
- 테이블별 인덱스 설정 목록
- 사용자 증가 시 발생할 수 있는 문제 상황과 개선 방안
- k6를 사용한 서버 부하 테스트
- 6차 스프린트 기준 ERD
- Query Performance Improvement Results
- 테스트 전략 및 CI 설정
- CI CD 구조
- 배포 전, 로컬에서 로그인 기능 포함 테스트해보는 법
- stylelint 적용기
- 내 작업 브랜치 중간에 Merge된 동료의 작업물을 넣고 싶다면 pull vs rebase
- [TS] Webpack config
- [TS] Webpack 환경에서 MSW v2 이슈
- [TS] webpack에서 react‐router‐dom 적용 안됨
- 2024.07.28 새 기획 회의
- 2024.07.26 2차 데모데이 후 회의
- 2024.07.11 백엔드 논의 좀 할게요
- 2024.07.11 백엔드 ERD 회의
- 2024.07.09 깃 브랜치 전략, PR 템플릿 회의
- 2024.07.03 주제 선정 회의
- 2023.07.03 팀빌딩데이 킥오프 회의
- 2023.08.07 3차 스프린트 중간회고