2011-03-03 30 views
9

我正在使用Spring的@ExceptionHandler註釋來捕獲我的控制器中的異常。從spring異常處理程序中讀取httprequest內容

一些請求將POST數據作爲純XML字符串寫入請求正文,我想讀取該數據以便記錄異常。 問題是,當我在異常處理程序中請求inputstream並嘗試從它讀取時,流返回-1(空)。

異常處理程序的簽名是:

@ExceptionHandler(Throwable.class) 
public ModelAndView exception(HttpServletRequest request, HttpServletResponse response, HttpSession session, Throwable arff) 

有什麼想法?有沒有辦法訪問請求體?

我的控制器:

@Controller 
@RequestMapping("/user/**") 
public class UserController { 

    static final Logger LOG = LoggerFactory.getLogger(UserController.class); 

    @Autowired 
    IUserService userService; 


    @RequestMapping("/user") 
    public ModelAndView getCurrent() { 
     return new ModelAndView("user","response", userService.getCurrent()); 
    } 

    @RequestMapping("/user/firstLogin") 
    public ModelAndView firstLogin(HttpSession session) { 
     userService.logUser(session.getId()); 
     userService.setOriginalAuthority(); 
     return new ModelAndView("user","response", userService.getCurrent()); 
    } 


    @RequestMapping("/user/login/failure") 
    public ModelAndView loginFailed() { 
     LOG.debug("loginFailed()"); 
     Status status = new Status(-1,"Bad login"); 
     return new ModelAndView("/user/login/failure", "response",status); 
    } 

    @RequestMapping("/user/login/unauthorized") 
    public ModelAndView unauthorized() { 
     LOG.debug("unauthorized()"); 
     Status status = new Status(-1,"Unauthorized.Please login first."); 
     return new ModelAndView("/user/login/unauthorized","response",status); 
    } 

    @RequestMapping("/user/logout/success") 
    public ModelAndView logoutSuccess() { 
     LOG.debug("logout()"); 
     Status status = new Status(0,"Successful logout"); 
     return new ModelAndView("/user/logout/success", "response",status); 

    } 

    @RequestMapping(value = "/user/{id}", method = RequestMethod.POST) 
    public ModelAndView create(@RequestBody UserDTO userDTO, @PathVariable("id") Long id) { 
     return new ModelAndView("user", "response", userService.create(userDTO, id)); 
    } 

    @RequestMapping(value = "/user/{id}", method = RequestMethod.GET) 
    public ModelAndView getUserById(@PathVariable("id") Long id) { 
     return new ModelAndView("user", "response", userService.getUserById(id)); 
    } 

    @RequestMapping(value = "/user/update/{id}", method = RequestMethod.POST) 
    public ModelAndView update(@RequestBody UserDTO userDTO, @PathVariable("id") Long id) { 
     return new ModelAndView("user", "response", userService.update(userDTO, id)); 
    } 

    @RequestMapping(value = "/user/all", method = RequestMethod.GET) 
    public ModelAndView list() { 
     return new ModelAndView("user", "response", userService.list()); 
    } 

    @RequestMapping(value = "/user/allowedAccounts", method = RequestMethod.GET) 
    public ModelAndView getAllowedAccounts() { 
     return new ModelAndView("user", "response", userService.getAllowedAccounts()); 
    } 

    @RequestMapping(value = "/user/changeAccount/{accountId}", method = RequestMethod.GET) 
    public ModelAndView changeAccount(@PathVariable("accountId") Long accountId) { 
     Status st = userService.changeAccount(accountId); 
     if (st.code != -1) { 
      return getCurrent(); 
     } 
     else { 
      return new ModelAndView("user", "response", st); 
     } 
    } 
    /* 
    @RequestMapping(value = "/user/logout", method = RequestMethod.GET) 
    public void perLogout(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
     userService.setOriginalAuthority(); 
     response.sendRedirect("/marketplace/user/logout/spring"); 
    } 
    */ 

    @ExceptionHandler(Throwable.class) 
public ModelAndView exception(HttpServletRequest request, HttpServletResponse response, HttpSession session, Throwable arff) { 
    Status st = new Status(); 
    try { 
     Writer writer = new StringWriter(); 
     byte[] buffer = new byte[1024]; 

     //Reader reader2 = new BufferedReader(new InputStreamReader(request.getInputStream())); 
     InputStream reader = request.getInputStream(); 
     int n; 
     while ((n = reader.read(buffer)) != -1) { 
      writer.toString(); 

     } 
     String retval = writer.toString(); 
     retval = ""; 
     } catch (IOException e) { 

      e.printStackTrace(); 
     } 

     return new ModelAndView("profile", "response", st); 
    } 
} 

謝謝

回答

9

我試過你的代碼,我發現在異常處理一些錯誤,當您從InputStream閱讀:

Writer writer = new StringWriter(); 
byte[] buffer = new byte[1024]; 

//Reader reader2 = new BufferedReader(new InputStreamReader(request.getInputStream())); 
InputStream reader = request.getInputStream(); 
int n; 
while ((n = reader.read(buffer)) != -1) { 
    writer.toString(); 

} 
String retval = writer.toString(); 
retval = ""; 

我已將此代碼替換爲此代碼:

BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream())); 
String line = ""; 
StringBuilder stringBuilder = new StringBuilder(); 
while ((line=reader.readLine()) != null) { 
    stringBuilder.append(line).append("\n"); 
} 

String retval = stringBuilder.toString(); 

然後我可以從異常處理程序中的InputStream中讀取它的工作原理! 如果您仍然無法從InputStream中讀取,我建議您檢查POST xml數據到請求主體的方式。 您應該考慮到每個請求只能使用一次Inputstream,所以我建議您檢查是否沒有任何其他電話撥打getInputStream()。如果您必須多次調用它,則應該編寫自定義HttpServletRequestWrapper以製作請求主體的副本,以便您多讀幾次。

UPDATE
您的意見幫助我重現了這個問題。您使用註釋@RequestBody,所以確實不要調用getInputStream(),但Spring會調用它來檢索請求的正文。看看org.springframework.web.bind.annotation.support.HandlerMethodInvoker這個班級:如果您使用@RequestBody這個班級調用resolveRequestBody方法,依此類推......最後,您不能再從ServletRequest中讀取InputStream了。如果您仍然想在自己的方法中同時使用@RequestBodygetInputStream(),則必須將請求包裝爲自定義HttpServletRequestWrapper以製作請求主體的副本,以便您可以手動讀取更多次。 這是我的包裝:

public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper { 

    private static final Logger logger = Logger.getLogger(CustomHttpServletRequestWrapper.class); 
    private final String body; 

    public CustomHttpServletRequestWrapper(HttpServletRequest request) { 
     super(request); 

     StringBuilder stringBuilder = new StringBuilder(); 
     BufferedReader bufferedReader = null; 

     try { 
      InputStream inputStream = request.getInputStream(); 
      if (inputStream != null) { 
       bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); 
       String line = ""; 
       while ((line = bufferedReader.readLine()) != null) { 
        stringBuilder.append(line).append("\n"); 
       } 
      } else { 
       stringBuilder.append(""); 
      } 
     } catch (IOException ex) { 
      logger.error("Error reading the request body..."); 
     } finally { 
      if (bufferedReader != null) { 
       try { 
        bufferedReader.close(); 
       } catch (IOException ex) { 
        logger.error("Error closing bufferedReader..."); 
       } 
      } 
     } 

     body = stringBuilder.toString(); 
    } 

    @Override 
    public ServletInputStream getInputStream() throws IOException { 
     final StringReader reader = new StringReader(body); 
     ServletInputStream inputStream = new ServletInputStream() { 
      public int read() throws IOException { 
       return reader.read(); 
      } 
     }; 
     return inputStream; 
    } 
} 

那麼你應該寫一個簡單的Filter包裝要求:

public class MyFilter implements Filter { 

    public void init(FilterConfig fc) throws ServletException { 

    } 

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 
     chain.doFilter(new CustomHttpServletRequestWrapper((HttpServletRequest)request), response); 

    } 

    public void destroy() { 

    } 

} 

最後,你必須配置你的過濾器在你的web.xml:

<filter>  
    <filter-name>MyFilter</filter-name> 
    <filter-class>test.MyFilter</filter-class> 
</filter> 
<filter-mapping> 
    <filter-name>MyFilter</filter-name> 
    <url-pattern>/*</url-pattern> 
</filter-mapping> 

您只能爲真正需要它的控制器啓動過濾器,因此您應該根據需要更改url模式。

如果您僅需要一個控制器中的此功能,那麼當您通過@RequestBody註釋接收到請求時,您也可以在該控制器中創建請求主體的副本。

+0

感謝您的回覆,我仍然無法閱讀請求正文。沒有拋出異常,只是讀者返回null意味着它是空的。也許我在春天錯過了關於異常處理程序的事情。如何在哪裏閱讀請求? – 2011-03-07 09:06:57

+0

我剛剛將您的代碼(通過更正)複製到新項目中。我通過enctype =「multipart/form-data」和輸入類型=「file」的靜態html表格來調用控制器來上傳文件。我添加了一個RequestMapping註釋和相應的方法,只拋出一個RuntimeException。在異常處理程序方法中,我可以記錄通過表單上傳的整個文件。當你進入控制器或者已經通過對getInputStream()的另一個調用使用時,似乎輸入流是空的。什麼版本的Spring? – javanna 2011-03-07 09:33:58

+0

@Noam Nevo有什麼消息嗎? :-) – javanna 2011-03-08 17:20:44

5

我有同樣的問題,並用HttpServletRequestWrapper解決它,如上所述,它很好。但後來,我發現了另一個擴展HttpMessageConverter的解決方案,在我的案例中是MappingJackson2HttpMessageConverter

public class CustomJsonHttpMessageConverter extends MappingJackson2HttpMessageConverter{ 

    public static final String REQUEST_BODY_ATTRIBUTE_NAME = "key.to.requestBody"; 


    @Override 
    public Object read(Type type, Class<?> contextClass, final HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { 

     final ByteArrayOutputStream writerStream = new ByteArrayOutputStream(); 

     HttpInputMessage message = new HttpInputMessage() { 
      @Override 
      public HttpHeaders getHeaders() { 
       return inputMessage.getHeaders(); 
      } 
      @Override 
      public InputStream getBody() throws IOException { 
       return new TeeInputStream(inputMessage.getBody(), writerStream); 
      } 
     }; 
        RequestContextHolder.getRequestAttributes().setAttribute(REQUEST_BODY_ATTRIBUTE_NAME, writerStream, RequestAttributes.SCOPE_REQUEST); 

     return super.read(type, contextClass, message); 
    } 

} 

com.sun.xml.internal.messaging.saaj.util.TeeInputStream被使用。

Spring MVC中配置

<mvc:annotation-driven > 
    <mvc:message-converters> 
     <bean class="com.company.remote.rest.util.CustomJsonHttpMessageConverter" /> 
    </mvc:message-converters> 
</mvc:annotation-driven> 

在@ExceptionHandler方法

@ExceptionHandler(Exception.class) 
public ResponseEntity<RestError> handleException(Exception e, HttpServletRequest httpRequest) { 

    RestError error = new RestError(); 
    error.setErrorCode(ErrorCodes.UNKNOWN_ERROR.getErrorCode()); 
    error.setDescription(ErrorCodes.UNKNOWN_ERROR.getDescription()); 
    error.setDescription(e.getMessage()); 


    logRestException(httpRequest, e); 

    ResponseEntity<RestError> responseEntity = new ResponseEntity<RestError>(error,HttpStatus.INTERNAL_SERVER_ERROR); 
    return responseEntity; 
} 

private void logRestException(HttpServletRequest request, Exception ex) { 
    StringWriter sb = new StringWriter(); 
    sb.append("Rest Error \n"); 
    sb.append("\nRequest Path"); 
    sb.append("\n----------------------------------------------------------------\n"); 
    sb.append(request.getRequestURL()); 
    sb.append("\n----------------------------------------------------------------\n"); 
Object requestBody = request.getAttribute(CustomJsonHttpMessageConverter.REQUEST_BODY_ATTRIBUTE_NAME); 

    if(requestBody != null) { 
     sb.append("\nRequest Body\n"); 
     sb.append("----------------------------------------------------------------\n"); 
     sb.append(requestBody.toString()); 

     sb.append("\n----------------------------------------------------------------\n"); 
    } 

    LOG.error(sb.toString()); 
} 

我希望它能幫助:)

+0

它......) – masadwin 2017-01-05 20:38:13

+0

TeeInputStream剛剛爲XML工作? – Frank 2017-01-06 07:03:14

2

最近我遇到這個問題,並解決它略有不同。使用彈簧啓動1.3.5.RELEASE

該過濾器是使用Spring類ContentCachingRequestWrapper實現的。這個包裝器有一個方法getContentAsByteArray()可以多次調用。

import org.springframework.web.util.ContentCachingRequestWrapper; 
public class RequestBodyCachingFilter implements Filter { 

    public void init(FilterConfig fc) throws ServletException { 
    } 

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 
     chain.doFilter(new ContentCachingRequestWrapper((HttpServletRequest)request), response); 
    } 

    public void destroy() { 
    } 
} 

添加過濾器鏈

@Bean 
public RequestBodyCachingFilter requestBodyCachingFilter() { 
    log.debug("Registering Request Body Caching filter"); 
    return new RequestBodyCachingFilter(); 
} 

在異常處理程序。

@ControllerAdvice(annotations = RestController.class) 
public class GlobalExceptionHandlingControllerAdvice { 
    private ContentCachingRequestWrapper getUnderlyingCachingRequest(ServletRequest request) { 
     if (ContentCachingRequestWrapper.class.isAssignableFrom(request.getClass())) { 
      return (ContentCachingRequestWrapper) request; 
     } 
     if (request instanceof ServletRequestWrapper) { 
      return getUnderlyingCachingRequest(((ServletRequestWrapper)request).getRequest()); 
     } 
     return null; 
    } 

    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) 
    @ExceptionHandler(Throwable.class) 
    public @ResponseBody Map<String, String> conflict(Throwable exception, HttpServletRequest request) { 
     ContentCachingRequestWrapper underlyingCachingRequest = getUnderlyingCachingRequest(request); 
     String body = new String(underlyingCachingRequest.getContentAsByteArray(),Charsets.UTF_8); 
     .... 
    } 
} 
+0

到目前爲止乾淨/最好的答案,謝謝! – 2017-07-02 08:07:00