2017-08-29 12 views
1

J'ai les choses suivantes dans mon application Spring MVC:corps de réponse du journal après méthode du contrôleur asynchrone Spring MVC

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

Il génère simplement csv, mais si lentement que je dois faire ce asynchrone d'appel.

J'essaie de connecter tout le corps de la réponse de la manière suivante:

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

} 

Voici mon gestionnaire d'exception:

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

Ici Status est POJO simple et ApplicationException est une exception personnalisée classe.

Lorsque generateSlowlyCsv déclenche une exception, il est traité dans handleException mais rien n'est consigné et aucun corps n'est renvoyé au client. D'autres méthodes de contrôleur non-asynchrones enregistrent l'erreur (même la même) juste très bien et retournent le corps de la réponse. Lorsque csv est généré (je l'ai vu dans le débogueur) sans erreurs, l'appel se bloque simplement et je ne peux pas trouver où (il revient du futurable). Sans LoggingFilter tout fonctionne très bien mais sans les bûches bien sûr. Comment puis-je ne pas perdre le corps de la réponse lorsqu'une exception s'est produite et renvoyer csv quand il est généré?

Merci beaucoup!

P.S. De retour Callable et MvcAsyncTask de la méthode du contrôleur ne contribue pas trop

Répondre

0

J'ai fini avec la chose suivante:

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

} 

Et oui, je sais, il est terrible que l'enfer, mais au moins il travaille pour tous les méthodes de contrôleur @ResponseBody. Je ne pense pas (au moins pour l'instant) j'ai besoin d'autre chose. J'ai essayé d'écrire un filtre asynchrone mais je n'ai pas réussi à ajouter AsyncListener à AsyncContext (veuillez voir the following question pour plus de détails). J'espère que cela vous aide.