266 lines
18 KiB
HTML
266 lines
18 KiB
HTML
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RequestExceptionHandler.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">project-auth-server</a> > <a href="index.source.html" class="el_package">com.project.auth.presentation.support.exception</a> > <span class="el_source">RequestExceptionHandler.java</span></div><h1>RequestExceptionHandler.java</h1><pre class="source lang-java linenums">package com.project.auth.presentation.support.exception;
|
|
|
|
import com.project.auth.application.support.exception.CommonErrorCode;
|
|
import com.project.auth.application.support.logging.LogSanitizer;
|
|
import com.project.auth.presentation.support.response.ApiResult;
|
|
import com.project.auth.presentation.support.response.ApiResultFactory;
|
|
import jakarta.servlet.http.HttpServletRequest;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.springframework.core.annotation.Order;
|
|
import org.springframework.beans.TypeMismatchException;
|
|
import org.springframework.core.Ordered;
|
|
import org.springframework.http.HttpStatusCode;
|
|
import org.springframework.http.ResponseEntity;
|
|
import org.springframework.http.converter.HttpMessageNotReadableException;
|
|
import org.springframework.web.ErrorResponse;
|
|
import org.springframework.web.ErrorResponseException;
|
|
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
|
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
|
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
|
import org.springframework.web.bind.MissingServletRequestParameterException;
|
|
import org.springframework.web.bind.MissingRequestHeaderException;
|
|
import org.springframework.web.bind.ServletRequestBindingException;
|
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|
import org.springframework.web.multipart.MaxUploadSizeExceededException;
|
|
import org.springframework.web.server.ResponseStatusException;
|
|
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
|
|
|
@RestControllerAdvice
|
|
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
|
|
public class RequestExceptionHandler {
|
|
|
|
<span class="fc" id="L34"> private static final Logger log = LoggerFactory.getLogger(RequestExceptionHandler.class);</span>
|
|
|
|
private final ApiResultFactory apiResultFactory;
|
|
|
|
<span class="fc" id="L38"> public RequestExceptionHandler(ApiResultFactory apiResultFactory) {</span>
|
|
<span class="fc" id="L39"> this.apiResultFactory = apiResultFactory;</span>
|
|
<span class="fc" id="L40"> }</span>
|
|
|
|
@ExceptionHandler(HttpMessageNotReadableException.class)
|
|
public ResponseEntity<ApiResult<Void>> handleMessageNotReadableException(
|
|
HttpMessageNotReadableException exception,
|
|
HttpServletRequest request
|
|
) {
|
|
<span class="fc" id="L47"> log.warn("Request body not readable. method={} requestPath={} errorCode={}",</span>
|
|
<span class="fc" id="L48"> request.getMethod(),</span>
|
|
<span class="fc" id="L49"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
|
<span class="fc" id="L50"> PresentationErrorCode.INVALID_REQUEST_BODY.code());</span>
|
|
|
|
<span class="fc" id="L52"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.INVALID_REQUEST_BODY))</span>
|
|
<span class="fc" id="L53"> .body(apiResultFactory.failure(</span>
|
|
<span class="fc" id="L54"> PresentationErrorCode.INVALID_REQUEST_BODY.code(),</span>
|
|
<span class="fc" id="L55"> PresentationErrorCode.INVALID_REQUEST_BODY.message()</span>
|
|
));
|
|
}
|
|
|
|
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
|
public ResponseEntity<ApiResult<Void>> handleMethodNotSupportedException(
|
|
HttpRequestMethodNotSupportedException exception,
|
|
HttpServletRequest request
|
|
) {
|
|
<span class="fc" id="L64"> log.warn("Method not supported. method={} requestPath={} attemptedMethod={} errorCode={}",</span>
|
|
<span class="fc" id="L65"> request.getMethod(),</span>
|
|
<span class="fc" id="L66"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
|
<span class="fc" id="L67"> LogSanitizer.normalize(exception.getMethod()),</span>
|
|
<span class="fc" id="L68"> PresentationErrorCode.METHOD_NOT_ALLOWED.code());</span>
|
|
|
|
<span class="fc" id="L70"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.METHOD_NOT_ALLOWED))</span>
|
|
<span class="fc" id="L71"> .body(apiResultFactory.failure(</span>
|
|
<span class="fc" id="L72"> PresentationErrorCode.METHOD_NOT_ALLOWED.code(),</span>
|
|
<span class="fc" id="L73"> PresentationErrorCode.METHOD_NOT_ALLOWED.message()</span>
|
|
));
|
|
}
|
|
|
|
@ExceptionHandler(MissingServletRequestParameterException.class)
|
|
public ResponseEntity<ApiResult<Void>> handleMissingParameterException(
|
|
MissingServletRequestParameterException exception,
|
|
HttpServletRequest request
|
|
) {
|
|
<span class="fc" id="L82"> log.warn("Missing request parameter. method={} requestPath={} parameter={} errorCode={}",</span>
|
|
<span class="fc" id="L83"> request.getMethod(),</span>
|
|
<span class="fc" id="L84"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
|
<span class="fc" id="L85"> LogSanitizer.normalize(exception.getParameterName()),</span>
|
|
<span class="fc" id="L86"> PresentationErrorCode.MISSING_PARAMETER.code());</span>
|
|
|
|
<span class="fc" id="L88"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.MISSING_PARAMETER))</span>
|
|
<span class="fc" id="L89"> .body(apiResultFactory.failure(</span>
|
|
<span class="fc" id="L90"> PresentationErrorCode.MISSING_PARAMETER.code(),</span>
|
|
<span class="fc" id="L91"> PresentationErrorCode.MISSING_PARAMETER.message()</span>
|
|
));
|
|
}
|
|
|
|
@ExceptionHandler(TypeMismatchException.class)
|
|
public ResponseEntity<ApiResult<Void>> handleTypeMismatchException(
|
|
TypeMismatchException exception,
|
|
HttpServletRequest request
|
|
) {
|
|
<span class="fc" id="L100"> log.warn("Type mismatch for parameter. method={} requestPath={} parameter={} errorCode={}",</span>
|
|
<span class="fc" id="L101"> request.getMethod(),</span>
|
|
<span class="fc" id="L102"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
|
<span class="fc" id="L103"> LogSanitizer.normalize(exception.getPropertyName()),</span>
|
|
<span class="fc" id="L104"> PresentationErrorCode.TYPE_MISMATCH.code());</span>
|
|
|
|
<span class="fc" id="L106"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.TYPE_MISMATCH))</span>
|
|
<span class="fc" id="L107"> .body(apiResultFactory.failure(</span>
|
|
<span class="fc" id="L108"> PresentationErrorCode.TYPE_MISMATCH.code(),</span>
|
|
<span class="fc" id="L109"> PresentationErrorCode.TYPE_MISMATCH.message()</span>
|
|
));
|
|
}
|
|
|
|
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
|
|
public ResponseEntity<ApiResult<Void>> handleMediaTypeNotSupportedException(
|
|
HttpMediaTypeNotSupportedException exception,
|
|
HttpServletRequest request
|
|
) {
|
|
<span class="fc" id="L118"> log.warn("Unsupported media type. method={} requestPath={} contentType={} errorCode={}",</span>
|
|
<span class="fc" id="L119"> request.getMethod(),</span>
|
|
<span class="fc" id="L120"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
|
<span class="fc" id="L121"> LogSanitizer.normalize(String.valueOf(exception.getContentType())),</span>
|
|
<span class="fc" id="L122"> PresentationErrorCode.UNSUPPORTED_MEDIA_TYPE.code());</span>
|
|
|
|
<span class="fc" id="L124"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.UNSUPPORTED_MEDIA_TYPE))</span>
|
|
<span class="fc" id="L125"> .body(apiResultFactory.failure(</span>
|
|
<span class="fc" id="L126"> PresentationErrorCode.UNSUPPORTED_MEDIA_TYPE.code(),</span>
|
|
<span class="fc" id="L127"> PresentationErrorCode.UNSUPPORTED_MEDIA_TYPE.message()</span>
|
|
));
|
|
}
|
|
|
|
@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
|
|
public ResponseEntity<ApiResult<Void>> handleMediaTypeNotAcceptableException(
|
|
HttpMediaTypeNotAcceptableException exception,
|
|
HttpServletRequest request
|
|
) {
|
|
<span class="fc" id="L136"> log.warn("Not acceptable media type. method={} requestPath={} errorCode={}",</span>
|
|
<span class="fc" id="L137"> request.getMethod(),</span>
|
|
<span class="fc" id="L138"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
|
<span class="fc" id="L139"> PresentationErrorCode.NOT_ACCEPTABLE.code());</span>
|
|
|
|
<span class="fc" id="L141"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.NOT_ACCEPTABLE))</span>
|
|
<span class="fc" id="L142"> .body(apiResultFactory.failure(</span>
|
|
<span class="fc" id="L143"> PresentationErrorCode.NOT_ACCEPTABLE.code(),</span>
|
|
<span class="fc" id="L144"> PresentationErrorCode.NOT_ACCEPTABLE.message()</span>
|
|
));
|
|
}
|
|
|
|
@ExceptionHandler(MissingRequestHeaderException.class)
|
|
public ResponseEntity<ApiResult<Void>> handleMissingRequestHeaderException(
|
|
MissingRequestHeaderException exception,
|
|
HttpServletRequest request
|
|
) {
|
|
<span class="fc" id="L153"> log.warn("Missing request header. method={} requestPath={} header={} errorCode={}",</span>
|
|
<span class="fc" id="L154"> request.getMethod(),</span>
|
|
<span class="fc" id="L155"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
|
<span class="fc" id="L156"> LogSanitizer.normalize(exception.getHeaderName()),</span>
|
|
<span class="fc" id="L157"> PresentationErrorCode.MISSING_HEADER.code());</span>
|
|
|
|
<span class="fc" id="L159"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.MISSING_HEADER))</span>
|
|
<span class="fc" id="L160"> .body(apiResultFactory.failure(</span>
|
|
<span class="fc" id="L161"> PresentationErrorCode.MISSING_HEADER.code(),</span>
|
|
<span class="fc" id="L162"> PresentationErrorCode.MISSING_HEADER.message()</span>
|
|
));
|
|
}
|
|
|
|
@ExceptionHandler(ServletRequestBindingException.class)
|
|
public ResponseEntity<ApiResult<Void>> handleServletRequestBindingException(
|
|
ServletRequestBindingException exception,
|
|
HttpServletRequest request
|
|
) {
|
|
<span class="fc" id="L171"> log.warn("Request binding failed. method={} requestPath={} errorCode={}",</span>
|
|
<span class="fc" id="L172"> request.getMethod(),</span>
|
|
<span class="fc" id="L173"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
|
<span class="fc" id="L174"> PresentationErrorCode.REQUEST_BINDING_FAILED.code());</span>
|
|
|
|
<span class="fc" id="L176"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.REQUEST_BINDING_FAILED))</span>
|
|
<span class="fc" id="L177"> .body(apiResultFactory.failure(</span>
|
|
<span class="fc" id="L178"> PresentationErrorCode.REQUEST_BINDING_FAILED.code(),</span>
|
|
<span class="fc" id="L179"> PresentationErrorCode.REQUEST_BINDING_FAILED.message()</span>
|
|
));
|
|
}
|
|
|
|
@ExceptionHandler(NoResourceFoundException.class)
|
|
public ResponseEntity<ApiResult<Void>> handleNoResourceFoundException(
|
|
NoResourceFoundException exception,
|
|
HttpServletRequest request
|
|
) {
|
|
<span class="fc" id="L188"> log.warn("No resource found. method={} requestPath={} resourcePath={} errorCode={}",</span>
|
|
<span class="fc" id="L189"> request.getMethod(),</span>
|
|
<span class="fc" id="L190"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
|
<span class="fc" id="L191"> LogSanitizer.requestPath(exception.getResourcePath()),</span>
|
|
<span class="fc" id="L192"> PresentationErrorCode.RESOURCE_NOT_FOUND.code());</span>
|
|
|
|
<span class="fc" id="L194"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.RESOURCE_NOT_FOUND))</span>
|
|
<span class="fc" id="L195"> .body(apiResultFactory.failure(</span>
|
|
<span class="fc" id="L196"> PresentationErrorCode.RESOURCE_NOT_FOUND.code(),</span>
|
|
<span class="fc" id="L197"> PresentationErrorCode.RESOURCE_NOT_FOUND.message()</span>
|
|
));
|
|
}
|
|
|
|
@ExceptionHandler(MaxUploadSizeExceededException.class)
|
|
public ResponseEntity<ApiResult<Void>> handleMaxUploadSizeExceededException(
|
|
MaxUploadSizeExceededException exception,
|
|
HttpServletRequest request
|
|
) {
|
|
<span class="fc" id="L206"> log.warn("Upload payload too large. method={} requestPath={} maxBytes={} errorCode={}",</span>
|
|
<span class="fc" id="L207"> request.getMethod(),</span>
|
|
<span class="fc" id="L208"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
|
<span class="fc" id="L209"> exception.getMaxUploadSize(),</span>
|
|
<span class="fc" id="L210"> PresentationErrorCode.PAYLOAD_TOO_LARGE.code());</span>
|
|
|
|
<span class="fc" id="L212"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.PAYLOAD_TOO_LARGE))</span>
|
|
<span class="fc" id="L213"> .body(apiResultFactory.failure(</span>
|
|
<span class="fc" id="L214"> PresentationErrorCode.PAYLOAD_TOO_LARGE.code(),</span>
|
|
<span class="fc" id="L215"> PresentationErrorCode.PAYLOAD_TOO_LARGE.message()</span>
|
|
));
|
|
}
|
|
|
|
/**
|
|
* 더 구체적인 핸들러로 잡히지 않은, 프레임워크가 직접 던진 상태 캐리어 예외 처리.
|
|
* 상위에서 결정된 4xx vs 5xx 분기가 클라이언트까지 보존되도록 carriedStatus를 그대로 사용한다.
|
|
*
|
|
* 본문 분류: 5xx는 내부 분류 노출을 막기 위해 COMMON-999로 정규화하고, 4xx는
|
|
* UNHANDLED_CLIENT_ERROR로 폴백한다. 클라이언트 SDK가 "요청 잘못" vs "서버 결함"을
|
|
* 여전히 구분할 수 있게 하기 위함이다.
|
|
*
|
|
* ResponseStatusException과 ErrorResponseException은 둘 다 {@link ErrorResponse}를 구현하면서
|
|
* 동시에 {@link Throwable}을 상속하므로 로그 호출부의 (Throwable) 캐스트가 안전하다.
|
|
* 향후 Throwable이 아닌 ErrorResponse 구현체를 이 @ExceptionHandler 목록에 추가하면
|
|
* 런타임 ClassCastException이 발생하므로, 이 핸들러는 명시한 두 예외 타입으로만 한정한다.
|
|
*/
|
|
@ExceptionHandler({ResponseStatusException.class, ErrorResponseException.class})
|
|
public ResponseEntity<ApiResult<Void>> handleErrorResponseException(
|
|
ErrorResponse exception,
|
|
HttpServletRequest request
|
|
) {
|
|
<span class="fc" id="L237"> HttpStatusCode carriedStatus = exception.getStatusCode();</span>
|
|
<span class="fc" id="L238"> int statusValue = carriedStatus.value();</span>
|
|
|
|
<span class="fc bfc" id="L240" title="All 2 branches covered."> if (statusValue >= 500) {</span>
|
|
<span class="fc" id="L241"> log.error("Framework-thrown 5xx status. method={} requestPath={} status={}",</span>
|
|
<span class="fc" id="L242"> request.getMethod(),</span>
|
|
<span class="fc" id="L243"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
|
<span class="fc" id="L244"> statusValue,</span>
|
|
(Throwable) exception);
|
|
<span class="fc" id="L246"> return ResponseEntity.status(carriedStatus)</span>
|
|
<span class="fc" id="L247"> .body(apiResultFactory.failure(</span>
|
|
<span class="fc" id="L248"> CommonErrorCode.INTERNAL_SERVER_ERROR.code(),</span>
|
|
<span class="fc" id="L249"> CommonErrorCode.INTERNAL_SERVER_ERROR.message()</span>
|
|
));
|
|
}
|
|
|
|
<span class="fc" id="L253"> log.warn("Framework-thrown 4xx status. method={} requestPath={} status={} errorCode={}",</span>
|
|
<span class="fc" id="L254"> request.getMethod(),</span>
|
|
<span class="fc" id="L255"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
|
<span class="fc" id="L256"> statusValue,</span>
|
|
<span class="fc" id="L257"> PresentationErrorCode.UNHANDLED_CLIENT_ERROR.code());</span>
|
|
<span class="fc" id="L258"> return ResponseEntity.status(carriedStatus)</span>
|
|
<span class="fc" id="L259"> .body(apiResultFactory.failure(</span>
|
|
<span class="fc" id="L260"> PresentationErrorCode.UNHANDLED_CLIENT_ERROR.code(),</span>
|
|
<span class="fc" id="L261"> PresentationErrorCode.UNHANDLED_CLIENT_ERROR.message()</span>
|
|
));
|
|
}
|
|
|
|
}
|
|
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.14.202510111229</span></div></body></html> |