Spring-web-4.3.3を含むSpring Boot 1.4.1を使用しています。サービスコードによってスローされた例外を処理する_@ControllerAdvice
_アノテーションが付けられたクラスと_@ExceptionHandler
_アノテーションが付けられたメソッドがあります。これらの例外を処理するとき、PUTおよびPOST操作の要求の一部であった_@RequestBody
_をログに記録したいので、ケースは診断に不可欠です。
Spring Docs ごとに_@ExceptionHandler
_メソッドのメソッドシグネチャには、HttpServletRequest
を含むさまざまなものを含めることができます。リクエストボディは通常、ここからgetInputStream()
またはgetReader()
を介して取得できますが、私のコントローラーメソッドが私のすべてのように_"@RequestBody Foo fooBody"
_のようなリクエストボディを解析する場合、_HttpServletRequest's
_入力ストリームまたはリーダーは、例外ハンドラーメソッドが呼び出されるまでに既に閉じられています。本質的に、リクエストの本文は既に説明されている問題と同様に、Springによって既に読み取られています here 。リクエストボディは一度しか読み取れないことがサーブレットでの作業でよくある問題です。
残念ながら、_@RequestBody
_は例外ハンドラメソッドで使用可能なオプションの1つではありません。
InputStream
を例外ハンドラメソッドに追加できますが、これはHttpServletRequestのInputStreamと同じものになるため、同じ問題が発生します。
また、_((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
_を使用して現在のリクエストを取得しようとしましたが、これは現在のリクエストを取得するための別のトリックですが、これはSpringが例外ハンドラメソッドに渡すHttpServletRequestと同じになるため、同じ問題が発生します。
this や this のようないくつかのソリューションについて読んだことがあります。これらのソリューションでは、要求の内容を読み取り、キャッシュできるフィルターチェーンにカスタム要求ラッパーを挿入します複数回読んでください。ロギングを実装するためだけにフィルター/リクエスト/レスポンスチェーン全体を中断したり(潜在的にパフォーマンスや安定性の問題を引き起こしたり)、アップロードされたドキュメントなどの大きなリクエストがある場合(これは私はそうする)、私はそれをメモリにキャッシュしたくない。それに、もし私が見つけられただけなら、Springはおそらく_@RequestBody
_を既にどこかにキャッシュしているでしょう。
ちなみに、多くのソリューションはContentCachingRequestWrapper
Springクラスの使用を推奨していますが、私の経験ではこれは機能しません。文書化されていないことを除けば、ソースコードを見ると、リクエストボディではなくパラメータのみをキャッシュしているように見えます。このクラスからリクエストの本文を取得しようとすると、常に空の文字列になります。
だから私は見逃したかもしれない他のオプションを探しています。読んでくれてありがとう。
リクエストボディオブジェクトをリクエストスコープBeanに参照できます。次に、そのリクエストスコープBeanを例外ハンドラに挿入して、リクエスト本文(または参照する他のリクエストコンテキスト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;
}
}
受け入れられた答えは、物事をやり取りするための新しい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);
RequestBodyAdvice インターフェースを使用して、リクエスト本文のコンテンツを取得できるはずです。 @ ControllerAdvice アノテーションが付けられたクラスにこれを実装すると、自動的に取得されるはずです。
HTTPメソッドやクエリパラメータなどの他のリクエスト情報を取得するには、 interceptor を使用します。私は 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;
}
}