I've got the following things in my Spring MVC application:
@RestController
public class SomeController {
@GetMapping(value = "/csv", produces = { "text/csv", MediaType.APPLICATION_JSON_VALUE })
public Future someAsyncMethod() {
return CompletableFuture
.supplyAsync(() -> generateCsvSlowly()))
.thenApply(csv -> {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Content-Disposition", "attachment; filename=" + "Filename_.csv");
httpHeaders.add("Cookie", "fileDownload=true; path=/");
return new HttpEntity<>(csv, httpHeaders);
});
}
}
}
So it simply generates csv but so slowly that I have to make this call asynchronous.
I'm trying to log all the response body in the following way:
@Component
public class LoggingFilter extends OncePerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingFilter.class);
private static final AtomicLong ID = new AtomicLong();
static final String SOME_FORMAT_STRING = ...;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
long id = ID.incrementAndGet();
HttpServletResponse responseToUse = response;
if (!(response instanceof ContentCachingResponseWrapper)) {
responseToUse = new ContentCachingResponseWrapper(response);
}
try {
filterChain.doFilter(request, responseToUse);
}
finally {
byte[] responseBodyBytes = ((ContentCachingResponseWrapper) responseToUse).getContentAsByteArray();
LOGGER.info(SOME_FORMAT_STRING, id, responseToUse.getStatus(), responseToUse.getContentType(),
new ServletServerHttpResponse(responseToUse).getHeaders(), new String(bodyBytes, UTF_8));
((ContentCachingResponseWrapper) responseToUse).copyBodyToResponse();
}
}
}
Here is my exception handler:
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(ApplicationException.class)
@ResponseBody
public ResponseEntity<Status> handleException(ApplicationException exception) {
Status status = new Status();
...
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
...
return new ResponseEntity(status, headers, exception.getHttpCodeMvc());
}
@Override
protected ResponseEntity handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleException(new ApplicationException(ex, ApplicationStatus.GENERIC_ERROR));
}
}
Here Status
is simple POJO and ApplicationException
is a custom exception class.
When generateSlowlyCsv
throws an exception it gets processed in handleException
but nothing get's logged and no body is returned to client. Other non-async controller methods log error (even the same one) just fine and return response body.
When csv is generated (I saw it in debugger) without errors the call simply hangs and I can't find where (it returns from completable future). Without LoggingFilter
everything works just fine but without logs of course.
How can I not loose response body when an exception occurred and return csv when it is generated? Thank you very much!
P.S. Returning Callable
and MvcAsyncTask
from the controller method doesn't help too
I ended with the following thing:
And yes, I know, it is terrible as hell but at least it is working for all the
@ResponseBody
controller methods. I don't think (at least for now) I need something else. I tried to write an asynchronous filter but I couldn't manage to addAsyncListener
toAsyncContext
(please, see the following question for details). Hope this helps you.