私は@Controllerメソッドが呼び出し可能インターフェースを返した場合、要求本体からフィルタで応答を読み取る方法を疑問に思って。
私のフィルタはこのようになります。応答は常に空です。これを任意のソリューション?これはAsyncListenerを使用してのみ許可されていますか?
@Component
public class ResposeBodyXmlValidator extends OncePerRequestFilter {
private final XmlUtils xmlUtils;
private final Resource xsdResource;
public ResposeBodyXmlValidator(
XmlUtils xmlUtils,
@Value("classpath:xsd/some.xsd") Resource xsdResource
) {
this.xmlUtils = xmlUtils;
this.xsdResource = xsdResource;
}
@Override
protected void doFilterInternal(
HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain
) throws ServletException, IOException {
ContentCachingResponseWrapper response = new ContentCachingResponseWrapper(httpServletResponse);
doFilter(httpServletRequest, response, filterChain);
if (MediaType.APPLICATION_XML.getType().equals(response.getContentType())) {
try {
xmlUtils.validate(new String(response.getContentAsByteArray(), response.getCharacterEncoding()), xsdResource.getInputStream());
} catch (IOException | SAXException e) {
String exceptionString = String.format("Chyba při volání %s\nNevalidní výstupní XML: %s",
httpServletRequest.getRemoteAddr(),
e.getMessage());
response.setContentType(MediaType.TEXT_PLAIN_VALUE + "; charset=UTF-8");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.getWriter().print(exceptionString);
}
}
response.copyBodyToResponse(); // I found this needs to be added at the end of the filter
}
}
コーラブルの問題は、ディスパッチャサーブレット自体は非同期処理を開始し、フィルタが実際に要求を処理する前に終了していることです。
呼び出し可能なディスパッチャサーブレットに到着すると、それはすべてのフィルタを解除することによって、プールからのコンテナのスレッドを解放します(フィルタは基本的に自分の仕事を終えます)。呼び出し可能な結果を生成する場合、ディスパッチャサーブレットが同じ要求で再び呼び出され、応答がimmidiatelyコーラブルからのデータリターンで満たされます。これは、タイプの要求属性によって処理されるAsyncTaskManager
非同期要求の処理に関するいくつかの情報を保持しています。これはでテストすることができますFilter
とHandlerInterceptor
。Filter
一度だけ実行されますが、HandlerInterceptor
2回実行される(元の要求とコーラブル後のリクエストは、その仕事を完了します)
あなたは、要求と応答を読み取る必要がある場合は、解決策の一つは、このようのDispatcherServletを書き換えることです。
@Bean
@Primary
public DispatcherServlet dispatcherServlet(WebApplicationContext context) {
return new DispatcherServlet(context) {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
super.service(requestWrapper, responseWrapper);
responseWrapper.copyBodyToResponse();
}
};
}
あなたは、要求と応答を複数回読むことができることを保証するこの方法です。他の事は(あなたが要求属性として、いくつかのデータを渡すために持っている)、このようHandlerInterceptorを追加することです。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
Exception {
Object asyncRequestData = request.getAttribute(LOGGER_FILTER_ATTRIBUTE);
if (asyncRequestData == null) {
request.setAttribute(LOGGER_FILTER_ATTRIBUTE, new AsyncRequestData(request));
}
return true;
}
@Override
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex
) throws Exception {
Object asyncRequestData = request.getAttribute(LOGGER_FILTER_ATTRIBUTE);
if (asyncRequestData != null && response instanceof ContentCachingResponseWrapper) {
log(request, (ContentCachingResponseWrapper) response, (AsyncRequestData) asyncRequestData);
}
}
afterCompletionの非同期要求が完全に処理された後の方法は、一度だけ呼ばれます。preHandleは、あなたの属性のexistanceをチェックする必要がありますので、正確に二度呼ばれています。でafterCompletion、コールからの応答がすでに存在している、あなたはそれを交換したいならば、あなたが呼び出す必要がありますresponse.resetBuffer()
。
これは、1つの可能なソリューションであり、より良い方法があるかもしれません。