Чтение содержимого httprequest из обработчика исключений Spring - PullRequest
13 голосов
/ 03 марта 2011

Я использую аннотацию Spring @ExceptionHandler для перехвата исключений в моих контроллерах.

В некоторых запросах данные POST хранятся в виде простой строки XML, записанной в теле запроса. Я хочу прочитать эти данные, чтобы записать исключение. Проблема в том, что когда я запрашиваю inputtream в обработчике исключений и пытаюсь прочитать из него, поток возвращает -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 ]

11 голосов
/ 07 марта 2011

Я попробовал ваш код и обнаружил некоторые ошибки в обработчике исключений, когда вы читали из 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-данные POST в тело запроса. Вам следует учитывать, что вы можете использовать Inputstream только один раз за запрос, поэтому я советую вам проверить, нет ли других вызовов getInputStream(). Если вам нужно вызывать его два или более раз, вы должны написать пользовательский HttpServletRequestWrapper, подобный этому, чтобы сделать копию тела запроса, чтобы вы могли читать его несколько раз.

UPDATE
Ваши комментарии помогли мне воспроизвести проблему. Вы используете аннотацию @RequestBody, поэтому верно, что вы не вызываете getInputStream(), но Spring вызывает ее, чтобы получить тело запроса. Взгляните на класс 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() {

    }

}

Наконец, вы должны настроить свой фильтр в файле 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.

8 голосов
/ 07 октября 2016

Недавно я столкнулся с этой проблемой и решил ее немного по-другому. С пружинным чехлом 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);
        ....
    }
}
5 голосов
/ 09 февраля 2013

У меня была та же проблема, и я решил ее с помощью 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 используется.

весной 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());
}

Надеюсь, это поможет:)

...