2

I want to log all requests and messages of request body in My REST Web Service Project. I use Spring and json message conveter. But I face with java.lang.IllegalStateException: getReader() has already been called for this request. How can I solve? Or Is there another way?

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/root-context.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <filter>
        <filter-name>LogFilter</filter-name>
        <filter-class>com.cmpolaris.system.LogFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>LogFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

spring-beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:util="http://www.springframework.org/schema/util" 
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->

    <!-- Enables the Spring MVC @Controller programming model -->
    <annotation-driven />

    <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
    <resources mapping="/resources/**" location="/resources/" />

    <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
    <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/views/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>

    <!-- Configure to plugin JSON as request and response in method handler -->
    <beans:bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <beans:property name="messageConverters">
            <beans:list>
                <beans:ref bean="jsonMessageConverter"/>
            </beans:list>
        </beans:property>
    </beans:bean>

    <!-- Configure bean to convert JSON to POJO and vice versa -->
    <beans:bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
    </beans:bean>   

    <context:component-scan base-package="com.cmpolaris" />

</beans:beans>

doFilter() of my Filter.

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {
        // Get the IP address of client machine.
        String ipAddress = request.getRemoteAddr();

        // Log the IP address and current timestamp.
        logger.info("IP " + ipAddress + ", Time " + new Date().toString());

        logger.info("Request Message");
        logger.info(CharStreams.toString(request.getReader()));

        // Pass request back down the filter chain
        chain.doFilter(request, response);

    }

I face with this exception

INFO : com.cmpolaris.system.LogFilter - IP 192.168.1.95, Time Wed Sep 03 13:23:31 MMT 2014
    INFO : com.cmpolaris.system.LogFilter - Request Message
    INFO : com.cmpolaris.system.LogFilter - {"userId":"KTT","pwd":"ktdt","devID":"","userGroup":"SaleMan"}
    Sep 3, 2014 1:23:31 PM org.apache.catalina.core.StandardWrapperValve invoke
    SEVERE: Servlet.service() for servlet [appServlet] in context with path [/cmpolaris] threw exception [Request processing failed; nested exception is java.lang.IllegalStateExcepti
    on: getReader() has already been called for this request] with root cause
    java.lang.IllegalStateException: getReader() has already been called for this request
        at org.apache.catalina.connector.Request.getInputStream(Request.java:1052)
        at org.apache.catalina.connector.RequestFacade.getInputStream(RequestFacade.java:368)
        at org.springframework.http.server.ServletServerHttpRequest.getBody(ServletServerHttpRequest.java:131)
        at org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.readJavaType(MappingJackson2HttpMessageConverter.java:178)
        at org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.read(MappingJackson2HttpMessageConverter.java:173)
        at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.
    java:143)
        at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:180)
        at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:95)
        at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:77)
        at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:162)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:123)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936)
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:838)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:304)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at com.cmpolaris.system.LogFilter.doFilter(LogFilter.java:32)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:240)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:164)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
        at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:562)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:394)
        at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:243)
        at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:188)
        at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:166)
        at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:302)
        at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at java.lang.Thread.run(Unknown Source)
Mthk
  • 118
  • 1
  • 2
  • 10
  • have a look at request wrapper http://stackoverflow.com/questions/1413129/modify-request-parameter-with-servlet-filter & BTW with getRemoteAddr - you cannot rely that it is the client's IP – Balaji Krishnan Sep 03 '14 at 07:22
  • What are you trying to do with the filter? You are calling getReader() in your filter, and your servlet tries to call getInputStream() somewhere after that (typically when something is rendered). Hence the exception. – NilsH Sep 03 '14 at 07:36
  • Yes, I'm just testing... I wanna get request body even this request is 400 Bad Request. I find as like how to get message for request body. They said getReader() and getInputStream(). But I am not ok. – Mthk Sep 03 '14 at 07:50
  • request wrapper will work fine here. Not sure if a better approach is available specific to spring – Balaji Krishnan Sep 03 '14 at 08:14
  • Spring already provides filters which do that. Take a look at the subclasses of the [`AbstractRequestLoggingFilter`](http://docs.spring.io/autorepo/docs/spring-framework/current/javadoc-api/org/springframework/web/filter/AbstractRequestLoggingFilter.html). No need to write your own. – M. Deinum Sep 03 '14 at 08:31
  • I already tried with **AbstractRequestLoggingFilter**. But Same Exception will appear. @M.Deinum – Mthk Sep 03 '14 at 08:36
  • Then you are doing something wrong with that filter. You should only configure it, nothing else would be needed. – M. Deinum Sep 03 '14 at 08:40
  • I made Custom Filter that extends _AbstractRequestLoggingFilter_. If only configure, can u tell me how? Please.. @M.Deinum – Mthk Sep 03 '14 at 08:52
  • As mentioned just use one of the subclasses and configure it in your web.xml. – M. Deinum Sep 03 '14 at 09:31

3 Answers3

5

Wrap your request object to another class before forwarding it to the chain filter. That way, you can avoid the used stream problem.

For example you can use this class (taken from here):

public class AuthenticationRequestWrapper extends HttpServletRequestWrapper {

    private final String payload;

    public AuthenticationRequestWrapper (HttpServletRequest request) throws AuthenticationException {
        super(request);

        // read the original payload into the payload variable
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = null;
        try {
            // read the payload into the StringBuilder
            InputStream inputStream = request.getInputStream();
            if (inputStream != null) {
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                char[] charBuffer = new char[128];
                int bytesRead = -1;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                    stringBuilder.append(charBuffer, 0, bytesRead);
                }
            } else {
                // make an empty string since there is no payload
                stringBuilder.append("");
            }
        } catch (IOException ex) {
            log.error("Error reading the request payload", ex);
            throw new AuthenticationException("Error reading the request payload", ex);
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException iox) {
                    // ignore
                }
            }
        }
        payload = stringBuilder.toString();
    }

    @Override
    public ServletInputStream getInputStream () throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(payload.getBytes());
        ServletInputStream inputStream = new ServletInputStream() {
            public int read () 
                throws IOException {
                return byteArrayInputStream.read();
            }
        };
        return inputStream;
    }
}

and in your first filter, wrap it first:

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        // use wrapper to read multiple times the content
        AuthenticationRequestWrapper request = new AuthenticationRequestWrapper((HttpServletRequest) req);
        HttpServletResponse response = (HttpServletResponse) resp;
        chain.doFilter(request,response);
}
dieend
  • 2,231
  • 1
  • 24
  • 29
4

Use one of the subclasses of the AbstractRequestLoggingFilter and set the includePayload property to true (the default is false).

Assuming you want to use the one that uses Commons Logging to log the messages add the CommonsRequestLoggingFilter to your web.xml and include a init param for the includePayload property.

<filter>
    <filter-name>LogFilter</filter-name>
    <filter-class>org.springframework.web.filter.CommonsRequestLoggingFilter</filter-class>
    <init-param>
        <param-name>includePayload</param-name>
        <param-value>true</param-value>
    </init-param>    
</filter>
<filter-mapping>
    <filter-name>LogFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

To also include more information about the client (user, sessionId, client) set the includeClientInfo property to true.

The messages are logged only when debug logging is on so make sure that the log level for the CommonsRequestLoggingFilter is at least debug.

The filter will wrap the request in a wrapper so that you can read the request multiple times.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
0

Do you know the Request Dumper Filter? According to your stack trace you're using tomcat, see http://tomcat.apache.org/tomcat-7.0-doc/config/filter.html#Request_Dumper_Filter for usage.

mp911de
  • 17,546
  • 2
  • 55
  • 95