2017-08-29 2 views
1

ich die folgenden Dinge in meinem Spring MVC-Anwendung haben:Log Antworttext nach asynchronen Spring MVC Controller-Methode

@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 erzeugt es einfach csv aber so langsam, dass ich diesen Anruf asynchron machen.

Ich versuche, alle Antworttext auf folgende Weise zu protokollieren:

@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(); 
     } 
    } 

} 

Hier ist meine 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)); 
    } 
} 

Hier Status ist einfach POJO und ApplicationException ist eine benutzerdefinierte Ausnahme Klasse.

Wenn generateSlowlyCsv eine Ausnahme auslöst, wird sie in handleException verarbeitet, aber nichts wird protokolliert, und es wird keine Nachricht an den Client zurückgegeben. Andere nicht-asynchrone Controller-Methoden protokollieren einen Fehler (auch den gleichen) und senden den Antworttext zurück. Wenn csv generiert wird (ich sah es im Debugger) ohne Fehler, hängt der Aufruf einfach und ich kann nicht finden, wo (es kommt von completable Zukunft zurück). Ohne LoggingFilter funktioniert alles gut, aber natürlich ohne Logs.

Wie kann ich Response-Body nicht verlieren, wenn eine Ausnahme aufgetreten ist und CSV zurückgeben, wenn es generiert wird? Vielen Dank!

P.S. Wiederkehr Callable und MvcAsyncTask aus der Controller-Methode auch nicht

Antwort

0

ich mit der folgenden Sache beendet hilft:

package com.ololo.filter; 

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import org.springframework.core.MethodParameter; 
import org.springframework.http.HttpHeaders; 
import org.springframework.http.HttpOutputMessage; 
import org.springframework.http.MediaType; 
import org.springframework.http.converter.HttpMessageConverter; 
import org.springframework.http.server.ServerHttpRequest; 
import org.springframework.http.server.ServerHttpResponse; 
import org.springframework.http.server.ServletServerHttpRequest; 
import org.springframework.http.server.ServletServerHttpResponse; 
import org.springframework.web.bind.annotation.ControllerAdvice; 
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; 
import org.springframework.web.util.ContentCachingRequestWrapper; 

import java.io.ByteArrayOutputStream; 
import java.io.IOException; 
import java.io.OutputStream; 
import java.util.concurrent.atomic.AtomicLong; 

import javax.servlet.http.HttpServletRequest; 

import static java.lang.System.lineSeparator; 
import static java.nio.charset.StandardCharsets.UTF_8; 

@ControllerAdvice 
public class LoggingAdvice implements ResponseBodyAdvice { 

    private static final Logger LOGGER = LoggerFactory.getLogger(LoggingAdvice.class); 
    private static final AtomicLong ID = new AtomicLong(); 

    private static final String SOME_RESPONSE_MESSAGE_FORMAT; 

    @Override 
    public boolean supports(MethodParameter returnType, Class converterType) { 
     return true; 
    } 

    @Override 
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { 
     long id = ID.incrementAndGet(); 

     ServletServerHttpResponse responseToUse = (ServletServerHttpResponse) response; 
     HttpMessageConverter httpMessageConverter; 
     LoggingHttpOutboundMessageWrapper httpOutputMessage = new LoggingHttpOutboundMessageWrapper(); 
     try { 
      httpMessageConverter = (HttpMessageConverter) selectedConverterType.newInstance(); 
      httpMessageConverter.write(body, selectedContentType, httpOutputMessage); 
      LOGGER.info(SOME_RESPONSE_MESSAGE_FORMAT, id, responseToUse.getServletResponse().getStatus(), responseToUse.getServletResponse().getContentType(), 
        responseToUse.getHeaders(), httpOutputMessage.getResponseBodyInString()); 
     } catch (InstantiationException | IllegalAccessException | IOException e) { 
      e.printStackTrace(); 
     } 

     return body; 
    } 

    private static final class LoggingHttpOutboundMessageWrapper implements HttpOutputMessage { 
     private HttpHeaders httpHeaders = new HttpHeaders(); 
     private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 

     @Override 
     public OutputStream getBody() throws IOException { 
      return byteArrayOutputStream; 
     } 

     @Override 
     public HttpHeaders getHeaders() { 
      return httpHeaders; 
     } 

     public String getResponseBodyInString() { 
      return new String(byteArrayOutputStream.toByteArray()); 
     } 
    } 

} 

Und ja, ich weiß, ist es wie die Hölle schrecklich, aber zumindest ist es Arbeit für alle die @ResponseBody Controller-Methoden. Ich denke nicht (zumindest für jetzt) ​​brauche ich etwas anderes. Ich habe versucht, einen asynchronen Filter zu schreiben, aber ich schaffte es nicht AsyncListener zu AsyncContext hinzuzufügen (bitte, siehe the following question für Details). Hoffe das hilft dir.