Чтение содержимого httprequest из обработчика исключений spring
Я использую весной @ExceptionHandler
аннотация для улавливания исключений в моих контроллерах.
некоторые запросы содержат данные 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);
}
}
спасибо
3 ответов
я попробовал ваш код и я нашел некоторые ошибки в обработчике исключений, когда читаешь с 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
, Я предлагаю вам проверить, как вы отправляете xml-данные в тело запроса.
Вы должны учитывать, что вы можете использовать Inputstream
только один раз в запрос, поэтому я предлагаю вам проверьте, нет ли другого вызова getInputStream()
. Если вам нужно вызвать его два или более раз, вы должны написать пользовательский HttpServletRequestWrapper
такой, чтобы скопировать текст запроса, так что вы можете прочитать его еще раз.
обновление
Ваши комментарии помогли мне воспроизвести проблему. Вы используете аннотацию @RequestBody, поэтому это правда, что вы не вызываете getInputStream()
, но весна вызывает его, чтобы получить тело запроса. Взгляните на класс org.springframework.web.bind.annotation.support.HandlerMethodInvoker
: если вы используете @RequestBody
этот класс вызывает resolveRequestBody
метод, и так далее... наконец, вы больше не можете читать InputStream
из своего ServletRequest
. Если вы все еще хотите использовать @RequestBody
и getInputStream()
в вашем собственном методе вы должны обернуть запрос в пользовательский 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() {
}
}
наконец, вы должны настроить ваш фильтр в вашей сети.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
Примечание.
у меня была такая же проблема и я решил ее с 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);
}
}
есть.
весной MVC config
<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());
}
надеюсь, это поможет:)
недавно я столкнулась с этой проблемой и решил ее немного по-другому. С пружинной загрузкой 1.3.5.Отпустите
фильтр был реализован с использованием класса 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);
....
}
}