Spring발행일 2024. 10. 11.원본 https://blog.naver.com/jword_/223612936972 ↗

HTTP 상태코드가 비현실적인 이유 (feat. RFC7807)

HTTP 상태코드가 비현실적인 이유 (feat. RFC7807) — #개발자의도구들 #RFC7807 #HTTPstatuscode #HTTP상태코드 AI스쿨 msa기반 java 백엔드 코스 ...

#Spring#Naver Blog

#개발자의도구들 #RFC7807 #HTTPstatuscode #HTTP상태코드

​

AI스쿨 msa기반 java 백엔드 코스 중에 공부한 내용을 작성하였습니다

\* 현재 Spring x 프로젝트를 진행하고 있습니다. 전체 목차를 보시려면 여기를 눌러주세요.

\* 해당 프로젝트 이전에 공부한 내용을 보시려면 여기를 눌러주세요.

이미지

🆕 UPDATED

해당글은 여러가지 설계 오류가 있어서 응답 처리에 대한 부분은 여기에 다시 정리하였습니다.

무엇을 return 해야할까

Spring Project를 진행하면서 정말 많이 고민했던 부분이 return입니다. 서버에서 발생한 오류를 어디까지 알려주는게 마땅한지에 대한 고민은 영원할 것 같습니다. 계속되는 고민끝에 느낀바는, 정답은 없는데 시간은 많이간다는 것입니다. 해당 문제를 고민할 시간을 줄이고 프로젝트 기능 구현에 더욱 집중하기 위해서 일관딘 패턴을 적용해야겠다는 생각을 하였습니다.

​

멘토님께 여줘본 결과 가장 현실적인 방법을 알려주셨는데 이번글에 한번 다뤄보고자 합니다. 이후 앞으로의 모든 프로젝트에 해당 방안을 우선 적용할 예정입니다.

​

우선 RFC7807에 대해 알아봅시다.

Index

  • RFC 7807
  • All OK()
  • Error Code
  • server
  • client
  • DTO
💡 Tips

RFC 7807

기존 HTTP Status Code는 문제점이 많습니다. 이에 대해 아래에 정리하였습니다.

text 코드 예제
                                    🙁 코드가 세자리 이다 : 서버에서 발생하는 오류가 999개 밖에 안될리가 없다.

🙁 코드가 친절하지 않다 : 클라이언트에게는 불친절해도 된다. 근데 Server에게 불친절하면 정확한
원인을 찾기가 어렵다.

🙁 애초에 코드에 의미 부여하는게 맞는 것일까?
모든 코드에 오류를 매핑해야하는데 이게 현실적인 방법인 걸까??

이 문제에 대한 해답을 RFC 7807이 어느정도 제시를 하고 있습니다. HTTP 응답 바디에 디테일한 오류 메시지를 JSON과 같은 형태로 전달하는 것입니다.

text 코드 예제
                                    {
    "type": 에러 분류를 위한 url식별
    "title": 에러에 대한 간략한 설명
    "status": http response code
    "detail": 에러에 대한 자세한 설명 // 서버 - 서버는 디버깅을 위해 최대한 자세하
    "instance": 에러 발생 근원 URI // 어떤 API에서 오류가 났는지
}

우선 서버간의 통신에서는 에러 메세지를 명확하게 제시해야 디버깅을 수월하게 할 수 있기 때문에 해당 형태로 오류를 전달하는 것은 매우 도움이 될 것 입니다.

java 코드 예제
                                     public ResponseEntity<Map<String, Object>> handleGenericException(Exception ex, WebRequest request) {

        Map<String, Object> problemDetails = new HashMap<>();
        problemDetails.put("type", URI.create("https://example.com/internal-error")); // 오류 유형 식별자
        problemDetails.put("title", "Internal Server Error");
        problemDetails.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
        problemDetails.put("detail", ex.getMessage());
        problemDetails.put("instance", request.getDescription(false));

        return new ResponseEntity<>(problemDetails, HttpStatus.INTERNAL_SERVER_ERROR);
    }

대강 이런식으로 구현이 가능한데 해당 구조가 완벽하다고 볼 수 없습니다. 이유는 아래와 같습니다.

text 코드 예제
                                    🙁 여전히 Code를 고민해야한다: HTTPCode를 적절하게 작성을 해야하는데, 이는 매우 힘든 작업입니다. (적절한 코드가 없는 경우가 더 많습니다)

🙁 오류지만 서버의 정상처리가 힘듭니다:  때로는 서버 내부에서 발생한 기능 오류를 정상으로 처리 해야하할 때가 있습니다.

위와 같은 문제 때문에 좀 더 현실적인 방법이 고안되었습니다.

All OK()

애초에 프로젝트 자체는 front-server 혹은 server-server간의 약속을 통해 만들어지는 것이기 때문에 내부에서 따로 에러코드를 만듭니다.

그러므로 실질적인 오류는 HTTP STATUS CODE가 아닌, 내부에서 서로 맞춰놓은 코드로 통신을 주고 받는게 더 낫습니다.

​

방법은 이러합니다.

text 코드 예제
                                    🤝모든 HTTP STATUS CODE는 OK로 전송합니다.

🤝서버간의 ERROR 정보는 RFC7807 표준을 따릅니다.

🤝RFC7807에 추가로 서로 맞춘 Error Code를 전송합니다.

* 이건 제 프로젝트에 적용한 부분
🤝 Client와는 사전에 맞춘 ErrorCdoe를 제시합니다. (단, 간단하게 설정)

​

Error Code

여기서 또 한번 고민되는 부분은 에러코드를 어떻게 설정해야하는지, 그리고 클라이언트에게 어떤 에러코드를 전송할지에 대한 부분입니다.

​

이를 위해 고안한 방법은 아래와 같습니다.

text 코드 예제
                                    🤝 서버 에러 코드는 HTTP STATUS CODE로 제시하는 대신, 구체적인 정보를 details 필드에 제공한다.

🤝 클라이언트 에러 코드는 별개의 코드로 등록순서로 정의하며, 최소한의 정보만을 포함한다.
    - 꼭 클라이언트에 필요한 정보만을 포함할 것 ‼️

​

⚠️ Server Error Code
text 코드 예제
                                    🤝 DB에러를 기술하지 말 것
🤝 서버가 정말 필요로 하는 정보를 제공할 것
kotlin 코드 예제
                                    @PostMapping("/live/like")
    fun save(
        @RequestParam boardId: Long,
        @RequestParam userId: Long
    ): ResponseEntity<ResponseToServerDto> {
        return try {
           likeService.save(boardId, userId)
                ResponseEntity.ok().body(
                    ResponseToServerDto(
                        error = null,
                        data = null
                    )
                )
        } catch (unknownIdError: UnknownBoardIdException) {
            ResponseEntity.ok().body(
                ResponseToServerDto(
                    error = ServerErrorDto(
                        type = "baseurl/errors/db-error",
                        status = HttpStatus.BAD_REQUEST.ordinal,
                        title = "DB Key Error",
                        detail = "Can't find the Board",
                        instance = "baseurl/like"
                    ),
                    data = null
                )
            )
        }
    }

이런식으로 모든 에러에 대해서 OK를 return하지만 BODY에는 구체적인 Error 정보를 제시하도록 하였습니다.

⚠️ Client Error Code
kotlin 코드 예제
                                    @PostMapping("/like")
    fun save(
        @RequestParam boardId: Long,
        @AuthenticationPrincipal user: AuthUserDetails
    ): ResponseEntity<ResponseToClientDto> {
        val isSaved: Boolean = likeService.save(boardId, user.getUserId()) // not null
        return if (isSaved) {
            ResponseEntity.ok().body(
                ResponseToClientDto(
                    errorCode = ResponseCode.SUCCESS,
                    data = null
                )
            )
        } else {
            ResponseEntity.ok().body(
                ResponseToClientDto(
                    errorCode = ResponseCode.SAVE_FAILURE,
                    data = null
                )
            )
        }
    }

Client는 errorCode를 따로 선언 후 전달합니다. 클라이언트 측에도 동일하게 Error Code를 정의해줘야 합니다.

​

레퍼런스

이미지

[RFC7807가 제시한 오류 메시지 생성하기
개요 목적 REST API에서 클라이언트 요청에 에러가 발생 했을 때, 서버는 HTTP status 에러 code를 보낸다. (400 bad request, 404 not found) 하지만, HTTP status code만으로 에러의 원인을 정확히 파악하기 힘들다. 그 때 사용하는 것이, Response Body에 에러에 대한 정보를 담은 오류 메시지를 보내는 것이다. 이제 문제는 오류 메세지에 어떤 내용을 담을 지 생각해야 한다. 이런 고생을 덜어주는 것이, RFC 7807 표준이다. 이번 시간에는 RFC7807 제시한 오류 메...
coding-business.tistory.com](https://coding-business.tistory.com/34)

​