2017-08-29 82 views
1

我已經得到了下面的事情在我的Spring MVC應用程序:登錄響應後身體異步的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); 
      }); 
     } 
    } 
} 

所以它只是生成CSV,但非常緩慢,以致我不得不做出這個調用異步的。

我試圖登錄下列方式中的所有響應正文:

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

} 

這裏是我的異常處理程序:

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

這裏Status是簡單的POJO和ApplicationException是一個自定義異常類。

generateSlowlyCsv引發異常時,它將在handleException中得到處理,但沒有得到記錄,也沒有主體返回給客戶端。其他非異步控制器方法記錄錯誤(甚至是同一個)就好了,並返回響應正文。 當生成csv(我在調試器中看到它)沒有錯誤時,調用簡單地掛起,我找不到在哪裏(它從可補充的未來返回)。沒有LoggingFilter一切正常,但沒有日誌當然。

當發生異常時,我怎樣才能鬆開響應主體,並在生成時返回csv?非常感謝你!

P.S.從控制器方法返回CallableMvcAsyncTask沒有幫助過

回答

0

我用下面的事情結束:

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

} 

是的,我知道,那是可怕的地獄,但至少它正在爲所有控制器方法@ResponseBody。我不認爲(至少現在)我需要別的東西。我試圖編寫一個異步過濾器,但我無法設法將AsyncListener添加到AsyncContext(請參閱the following question瞭解詳情)。希望這可以幫助你。