Function Object reference Return value Is extension function
let it Lambda result Yes
run this Lambda result Yes
run - Lambda result No: called without the context object
with this Lambda result No: takes the context object as an argument.
apply this Context object Yes
also it Context object Yes

다른 블로그 봤는데 이해가 안돼서 정리할려고 티스토리 켜고 공식문서 펼쳐봤는데
공식문서 설명이 젤 쉽네.. 심지어 영어인데, 그냥 저 표로 끝!

https://kotlinlang.org/docs/scope-functions.html#function-selection

takeIf, takeUnless 도 잘 써먹자!

1. 태그 목록 조회

curl -X GET http://{레지스트리 주소}/v2/{레파지토리명}/tags/list

1. 이미지 해시 조회

curl -sI -H "Accept: application/vnd.docker.distribution.manifest.v2+json" http://{레지스트리 주소}:32000/v2/{레파지토리명}/manifests/{태그명}

결과값에서 Docker-Content-Digest 확인

2. 삭제

curl -s -w "%{http_code}" -X DELETE http://{레지스트리 주소}/v2/{레파지토리명}/manifests/sha256:{다이제스트}

202 응답 받으면 성공

3. 가비지 컬렉트

모든 태그를 삭제 한 후

kubectl exec -n container-registry -it {파드명} -- sh

/bin/registry garbage-collect -m /etc/docker/registry/config.yml
rm -rf /var/lib/registry/docker/registry/v2/repositories/{레파지토리명}

결과 확인은 브라우저에서~

http://{레지스트리 주소}/v2/_catalog

 

자체 요구 사항

  • response의 http status를 원하는 코드로 응답할 수 있을 것
  • 커스텀 프로퍼티를 추가할 수 있을 것
  • Spring 기본 에러 응답과 동일한 형태의 response json 구조를 유지 할 것
  • 별도 라이브러리 안쓸것

공통 - 커스텀 프로퍼티 없는 경우

ResponseStatusException 사용

throw ResponseStatusException(HttpStatus.LOCKED, "잠겨있는 리소스 입니다.")

방법 1 - spring.mvc.problemdetails.enabled: true

application.yml 에 아래 설정을 추가한 뒤,

spring.mvc.problemdetails.enabled: true

아래와 같은 형태로 커스텀 익셉션을 구성해서 throw.

import org.springframework.http.HttpStatus
import org.springframework.web.ErrorResponseException

class UserAlreadyExistException : ErrorResponseException(
    HttpStatus.CONFLICT
) {
    init {
        this.body.setProperty("a", "2")
    }
}

이 경우 에러 response는 아래와 같은 형태가 된다.

{
  "type": "about:blank",
  "title": "Conflict",
  "status": 409,
  "instance": "/users/123123",
  "a": "2"
}

각 필드는 익셉션 안에서 ErrorResponseException을 override 하거나, init 블록에서 수정하는 식으로 변경 가능

단점 : stacktrace나 exception 종류를 확인 할 수 없음

방법 2 - spring.mvc.problemdetails.enabled: false (기본값)

response 필드가 조금 다름

{
  "timestamp": "2024-04-02T10:21:46.812+00:00",
  "status": 409,
  "error": "Conflict",
  "path": "/users/123123"
}

노출시킬 필드를 프로퍼티 파일로 설정 가능

server.error:
  include-message: always
  include-binding-errors: always
  include-stacktrace: always
  include-exception: true

모두 포함시키면 아래와 같이 출력됨

{
  "timestamp": "2024-04-02T10:13:12.616+00:00",
  "status": 409,
  "error": "Conflict",
  "exception": "***.service.user.UserAlreadyExistException",
  "trace": "***.service.user.UserAlreadyExistException: 409 CONFLICT, ProblemDetail[type='about:blank', title='Conflict', status=409, detail='null', instance='null', properties='{a=2}']\n\tat ***.service.user.UserService.addUser(UserService.kt:16)\n\tat ***.controller.UserController.addUser(UserController.kt:23)\n(생략)",
  "message": "409 CONFLICT, ProblemDetail[type='about:blank', title='Conflict', status=409, detail='null', instance='null', properties='{a=2}']",
  "path": "/users/121231"
}

단점 1) 커스텀 프로퍼티가 출력되지 않는다.

CustomErrorAttributes 컴포넌트를 구현해줘야 한다.

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

@Component
class CustomErrorAttributes: DefaultErrorAttributes() {
    override fun getErrorAttributes(webRequest: WebRequest?, options: ErrorAttributeOptions?): MutableMap<String, Any> {
        val attributes = super.getErrorAttributes(webRequest, options)
        val error = getError(webRequest)
        if (error is ErrorResponseException && error.body.properties != null) {
            attributes.putAll(error.body.properties!!)
        }
        return attributes
    }
}

단점 2) GET 메서드인 경우 브라우저에서 접근시 /error 매핑으로 이동하므로 아래와 같은 에러 화면이 보인다.

Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.

Tue Apr 02 18:57:04 KST 2024
There was an unexpected error (type=Locked, status=423).

아래와 같이 비활성화 시킬 수 있지만, 그러면 톰캣 정보가 출력 됨 (ResponseStatusException을 반환한 경우)

server.error.whitelabel.enabled: false

싫으면 ErrorController를 구현해야 함 (https://sh970901.tistory.com/132 참고)

방법 3 - @ExceptionHander 사용

Exception에 따로 어노테이션을 달거나 상속받거나 하지 않고 처리하는 방법

Controller에 아래 내용 추가

    @ExceptionHandler(UserAlreadyExistException::class)
    fun handle(e: UserAlreadyExistException): ProblemDetail {
        val problemDetail = ProblemDetail.forStatus(HttpStatus.CONFLICT)
        problemDetail.setProperty("a", "1")
        return problemDetail
    }

response 형태는 spring.mvc.problemdetails.enabled = true 일 때와 동일함

{
  "type": "about:blank",
  "title": "Conflict",
  "status": 409,
  "instance": "/users/new",
  "a": "1"
}

Controller 구분 없이 전역으로 적용 하고 싶을 땐

@RestControllerAdvice(annotations = RestController.class)
public class GlobalRestControllerAdvice {

    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(UserAlreadyExistsException::class)
    fun handle(e: UserAlreadyExistsException): ProblemDetail {

단점 : 1번 방법일때랑 동일..

결론

1. 제일 간단한 방법

throw ResponseStatusException(HttpStatus.LOCKED, "잠겨있는 리소스 입니다.")

2. throw 된 exception을 다른 로직에서 catch 해서 사용하기도 하는 경우

@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Data Not Found")
public class DataNotFoundException extends RuntimeException { }

3. custom property가 필요한 경우 (stack trace도 보고싶음)

import org.springframework.http.HttpStatus
import org.springframework.web.ErrorResponseException

class UserAlreadyExistException : ErrorResponseException(
    HttpStatus.CONFLICT
) {
    init {
        this.body.setProperty("a", "2")
    }
}
import org.springframework.boot.web.error.ErrorAttributeOptions
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes
import org.springframework.stereotype.Component
import org.springframework.web.ErrorResponseException
import org.springframework.web.context.request.WebRequest

@Component
class CustomErrorAttributes: DefaultErrorAttributes() {
    override fun getErrorAttributes(webRequest: WebRequest?, options: ErrorAttributeOptions?): MutableMap<String, Any> {
        val attributes = super.getErrorAttributes(webRequest, options)
        val error = getError(webRequest)
        if (error is ErrorResponseException && error.body.properties != null) {
            attributes.putAll(error.body.properties!!)
        }
        return attributes
    }
}

4. custom property가 필요한 경우 (단 특정 Controller에서만 사용, stack trace 안봄)

class DataNotFoundException(val type: String): RuntimeException() {
}

@ExceptionHandler(DataNotFoundException::class)
fun handle(e: DataNotFoundException): ProblemDetail {
    val problemDetail = ProblemDetail.forStatus(HttpStatus.CONFLICT)
    problemDetail.setProperty("type", e.type)
    return problemDetail
}

5. custom property가 필요한 경우(모든 Controller에서 사용, stack trace 안봄)

spring.mvc.problemdetails.enabled: true
import org.springframework.http.HttpStatus
import org.springframework.web.ErrorResponseException

class UserAlreadyExistException : ErrorResponseException(
    HttpStatus.CONFLICT
) {
    init {
        this.body.setProperty("a", "2")
    }
}

또는

@RestControllerAdvice(annotations = RestController.class)
public class GlobalRestControllerAdvice {

    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(UserAlreadyExistsException::class)
    fun handle(e: UserAlreadyExistsException): ProblemDetail {

 

 

+ Recent posts