@ExceptionHandler(Spring REST)에서 @RequestBody를 얻는 방법
spring-web-4.3이 포함된 Spring Boot 1.4.1을 사용하고 있습니다.에 대한 주석을 단 수업이 있다.@ControllerAdvice
및 으로 주석을 단 메서드@ExceptionHandler
서비스 코드에 의해 발생하는 예외를 처리합니다.이러한 예외를 처리할 때,@RequestBody
PUT 및 POST 운영 요청의 일부였기 때문에 문제의 원인이 된 요청 기관을 확인할 수 있습니다.이 경우 진단에 매우 중요합니다.
Per Spring Docs 메서드시그니처@ExceptionHandler
방법에는 다양한 것이 포함될 수 있습니다.HttpServletRequest
요청 본문은 보통 여기서 입수할 수 있습니다.getInputStream()
또는getReader()
단, 컨트롤러 메서드가 요청 본문을 다음과 같이 해석하는 경우"@RequestBody Foo fooBody"
우리 모두가 그렇듯이HttpServletRequest's
예외 핸들러 메서드가 호출될 때까지 입력 스트림 또는 판독기가 이미 닫혀 있습니다.기본적으로 요청 본문은 스프링에서 이미 읽었으며, 이는 여기에 설명된 문제와 유사합니다.요청 본문을 한 번만 읽을 수 있는 것은 서블릿을 사용하는 일반적인 문제입니다.
불행하게도@RequestBody
예외 핸들러 방식에서 사용할 수 있는 옵션 중 하나가 아닙니다.그렇다면 사용할 수 있습니다.
추가할 수 있습니다.InputStream
예외 핸들러 메서드로 이행합니다만, 이것은 Http ServletRequest의 InputStream과 같은 것이 되어, 같은 문제가 발생합니다.
저는 또한 현재의 요청을 받아보려고 했습니다.((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
이는 현재 요구를 취득하기 위한 또 다른 트릭이지만 결국 Spring이 예외 핸들러 메서드로 전달되는Http ServletRequest와 같은 문제가 발생하게 됩니다.
요청 내용을 읽고 여러 번 읽을 수 있도록 캐시하는 필터 체인에 커스텀 요청 래퍼를 삽입하는 것과 관련된 몇 가지 솔루션에 대해 읽은 적이 있습니다.이 솔루션은 단순히 로깅을 구현하기 위해 필터/요구/응답 체인 전체를 중단하고 싶지 않으며(또한 성능 또는 안정성 문제가 발생할 수 있음), 업로드된 문서와 같은 대규모 요청이 있는 경우 이를 메모리에 캐싱하고 싶지 않기 때문에 좋아하지 않습니다.게다가, 봄은 아마 그...@RequestBody
찾을 수만 있다면 이미 어딘가에 캐싱되어 있을 거야
, 솔루션에서는, 「 」를 사용하는 합니다.ContentCachingRequestWrapper
봄 수업이지만 내 경험상 이것은 효과가 없다.문서화되어 있지 않을 뿐만 아니라 소스 코드를 보면 요청 본문이 아닌 파라미터만 캐시하는 것처럼 보입니다.이 클래스에서 요청 본문을 가져오려고 하면 항상 빈 문자열이 생성됩니다.
그래서 제가 놓친 다른 선택지를 찾고 있습니다.읽어주셔서 감사합니다.
응답을 수락하면 전달하기 위한 새로운 POJO가 생성되지만 http 요청을 재사용하여 추가 개체를 생성하지 않고도 동일한 동작을 수행할 수 있습니다.
컨트롤러 매핑의 코드 예:
public ResponseEntity savePerson(@RequestBody Person person, WebRequest webRequest) {
webRequest.setAttribute("person", person, RequestAttributes.SCOPE_REQUEST);
그리고 나중에 ExceptionHandler 클래스/메서드에서는 다음을 사용할 수 있습니다.
@ExceptionHandler(Exception.class)
public ResponseEntity exceptionHandling(WebRequest request,Exception thrown) {
Person person = (Person) request.getAttribute("person", RequestAttributes.SCOPE_REQUEST);
request body 객체를 request scope bean으로 참조할 수 있습니다.그런 다음 요청 범위 빈을 예외 핸들러에 삽입하여 요청 본문(또는 참조하는 다른 요청 컨텍스트빈)을 가져옵니다.
// @Component
// @Scope("request")
@ManagedBean
@RequestScope
public class RequestContext {
// fields, getters, and setters for request-scoped beans
}
@RestController
@RequestMapping("/api/v1/persons")
public class PersonController {
@Inject
private RequestContext requestContext;
@Inject
private PersonService personService;
@PostMapping
public Person savePerson(@RequestBody Person person) throws PersonServiceException {
requestContext.setRequestBody(person);
return personService.save(person);
}
}
@ControllerAdvice
public class ExceptionMapper {
@Inject
private RequestContext requestContext;
@ExceptionHandler(PersonServiceException.class)
protected ResponseEntity<?> onPersonServiceException(PersonServiceException exception) {
Object requestBody = requestContext.getRequestBody();
// ...
return responseEntity;
}
}
RequestBodyAdvice 인터페이스를 사용하여 요청 본문의 내용을 가져올 수 있습니다.@ControllerAdvice에서 주석을 단 클래스에 이 기능을 구현하면 자동으로 선택됩니다.
HTTP 메서드 및 쿼리 매개 변수와 같은 다른 요청 정보를 가져오려면 인터셉터를 사용합니다.ThreadLocal 변수에서 오류 보고를 위해 이 모든 요청 정보를 캡처합니다.이 정보는 같은 인터셉터의 afterCompletion 후크에서 클리어합니다.
다음 클래스는 이 기능을 구현하고 ExceptionHandler에서 모든 요청 정보를 가져올 수 있습니다.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class RequestInfo extends HandlerInterceptorAdapter implements RequestBodyAdvice {
private static final Logger logger = LoggerFactory.getLogger(RequestInfo.class);
private static final ThreadLocal<RequestInfo> requestInfoThreadLocal = new ThreadLocal<>();
private String method;
private String body;
private String queryString;
private String ip;
private String user;
private String referrer;
private String url;
public static RequestInfo get() {
RequestInfo requestInfo = requestInfoThreadLocal.get();
if (requestInfo == null) {
requestInfo = new RequestInfo();
requestInfoThreadLocal.set(requestInfo);
}
return requestInfo;
}
public Map<String,String> asMap() {
Map<String,String> map = new HashMap<>();
map.put("method", this.method);
map.put("url", this.url);
map.put("queryParams", this.queryString);
map.put("body", this.body);
map.put("ip", this.ip);
map.put("referrer", this.referrer);
map.put("user", this.user);
return map;
}
private void setInfoFromRequest(HttpServletRequest request) {
this.method = request.getMethod();
this.queryString = request.getQueryString();
this.ip = request.getRemoteAddr();
this.referrer = request.getRemoteHost();
this.url = request.getRequestURI();
if (request.getUserPrincipal() != null) {
this.user = request.getUserPrincipal().getName();
}
}
public void setBody(String body) {
this.body = body;
}
private static void setInfoFrom(HttpServletRequest request) {
RequestInfo requestInfo = requestInfoThreadLocal.get();
if (requestInfo == null) {
requestInfo = new RequestInfo();
}
requestInfo.setInfoFromRequest(request);
requestInfoThreadLocal.set(requestInfo);
}
private static void clear() {
requestInfoThreadLocal.remove();
}
private static void setBodyInThreadLocal(String body) {
RequestInfo requestInfo = get();
requestInfo.setBody(body);
setRequestInfo(requestInfo);
}
private static void setRequestInfo(RequestInfo requestInfo) {
requestInfoThreadLocal.set(requestInfo);
}
// Implementation of HandlerInterceptorAdapter to capture the request info (except body) and be able to add it to the report in case of an error
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
RequestInfo.setInfoFrom(request);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) {
RequestInfo.clear();
}
// Implementation of RequestBodyAdvice to capture the request body and be able to add it to the report in case of an error
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return inputMessage;
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
RequestInfo.setBodyInThreadLocal(body.toString());
return body;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}
5인칭 답변에 대한 개선 사항입니다.
요청 본문을 입수하여 예외 핸들러 클래스 내 어디에서나 사용할 수 있습니다.
@ControllerAdvice
public class CustomErrorHandler extends ResponseEntityExceptionHandler implements RequestBodyAdvice {
...
private Object reqBody;
...
@ExceptionHandler(NoSuchElementException.class)
public ResponseEntity<Object> handleNoSuchElementException(final NoSuchElementException ex,
final WebRequest request) {
System.out.println("===================================" + reqBody);
return handleNotFoundException(ex, request);
}
...
@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
return inputMessage;
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
// capture request body here to use in our controller advice class
this.reqBody = body;
return body;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}
다음은 일부 필드 검증 제어에 사용한 Kotlin 구문 해결 방법입니다.
의 「Default(디폴트)」를 .handleMethodArgumentNotValid(...)
의 @RestControllerAdvice
동일한 요청 본문 개체에 포함된 필드를 체계적으로 기록합니다.
override fun handleMethodArgumentNotValid(e: MethodArgumentNotValidException, headers: HttpHeaders, status: HttpStatus, request: WebRequest): ResponseEntity<Any> {
val error = e.bindingResult.fieldErrors.first()
val requestBody = try {
val field = error.javaClass.getDeclaredField("violation").apply { trySetAccessible() }
((field[error] as ConstraintViolationImpl<Any>).rootBean as MyRequestBodyObject)
} catch (ex : Exception) {
//do some failsafe here
}
}
HttpServerRequest 에의 액세스 방법에 대해서는, 여기를 참조해 주세요.https://stackoverflow.com/a/61813076/1036433
언급URL : https://stackoverflow.com/questions/43502332/how-to-get-the-requestbody-in-an-exceptionhandler-spring-rest
'code' 카테고리의 다른 글
ajax success 콜백 함수 내의 $(this)에 액세스하는 방법 (0) | 2023.03.29 |
---|---|
Jackson Scala 모듈의 작은 예 (0) | 2023.03.29 |
jQuery-UI - "정의되지 않은 속성 '단계'를 읽을 수 없습니다" (0) | 2023.03.29 |
ASP.NET Core가 상태 코드와 함께 JSON을 반환합니다. (0) | 2023.03.29 |
여러 파일을 비스듬히 업로드하다 (0) | 2023.03.29 |