0

I would like to get the XML data from request and response and use it into Rest controller. I tried this:

@RestController()
    public class HomeController {

        @PostMapping(value = "/v1")
      public Response handleMessage(@RequestBody Transaction transaction, HttpServletRequest request, HttpServletResponse response) throws Exception {

           HttpServletRequest request, HttpServletResponse response

            System.out.println("!!!!!!! InputStream");
            System.out.println(request.getInputStream());
            System.out.println(response.getOutputStream());

            InputStream in = request.getInputStream();
            String readLine;
            BufferedReader br = new BufferedReader(new InputStreamReader(in));

            while (((readLine = br.readLine()) != null)) {
                System.out.println(readLine);    
            }
         } 
    }

But I get java.io.IOException: UT010029: Stream is closed

What is the proper way to get the content into String variable?

EDIT: I also tried solution with Filter but I'm not aware how to use the request payload into rest controller:

Read request payload:

@Component
public class HttpLoggingFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(HttpLoggingFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        ResettableStreamHttpServletRequest wrappedRequest = new ResettableStreamHttpServletRequest((HttpServletRequest) request);
        wrappedRequest.getInputStream().read();
        String body = IOUtils.toString(wrappedRequest.getReader());
        System.out.println("!!!!!!!!!!!!!!!!!! " + body);
        wrappedRequest.resetInputStream();


        chain.doFilter(request, response);
    }

    public class ResettableStreamHttpServletRequest extends HttpServletRequestWrapper {
        private byte[] rawData;
        private HttpServletRequest request;
        private ResettableServletInputStream servletStream;

        ResettableStreamHttpServletRequest(HttpServletRequest request) {
            super(request);
            this.request = request;
            this.servletStream = new ResettableServletInputStream();
        }

        void resetInputStream() {
            servletStream.stream = new ByteArrayInputStream(rawData);
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            if (rawData == null) {
                rawData = IOUtils.toByteArray(this.request.getInputStream());
                servletStream.stream = new ByteArrayInputStream(rawData);
            }
            return servletStream;
        }

        @Override
        public BufferedReader getReader() throws IOException {
            if (rawData == null) {
                rawData = IOUtils.toByteArray(this.request.getInputStream());
                servletStream.stream = new ByteArrayInputStream(rawData);
            }
            String encoding = getCharacterEncoding();
            if (encoding != null) {
                return new BufferedReader(new InputStreamReader(servletStream, encoding));
            } else {
                return new BufferedReader(new InputStreamReader(servletStream));
            }
        }

        private class ResettableServletInputStream extends ServletInputStream {
            private InputStream stream;

            @Override
            public int read() throws IOException {
                return stream.read();
            }

            @Override
            public boolean isFinished() {
                // TODO Auto-generated method stub
                return false;
            }

            @Override
            public boolean isReady() {
                // TODO Auto-generated method stub
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
                // TODO Auto-generated method stub

            }
        }
    }   
}

Rest endpoint:

@RestController()
public class HomeController {

    @PostMapping(value = "/v1")
  public Response handleMessage(@RequestBody Transaction transaction, HttpServletRequest request, org.zalando.logbook.HttpRequest requestv, HttpServletResponse response) throws Exception {

       // Get here request and response and log it into DB
     } 
}

How I can call HttpLoggingFilter into the Java method handleMessage and get the request as body String? Probably I can make it service and Inject it? Can you give me some advice how I can assess the code?

Peter Penzov
  • 1,126
  • 134
  • 430
  • 808
  • Could you post the complete exception stack? – Bsquare ℬℬ Nov 25 '18 at 00:31
  • Where are you trying to do this? Please add some more context. The point in time at which you're trying to do it matters, as the stream could already have been read by some other process. Also maybe we can offer some alternatives if there is some more context. Oh and what framework are you using. Please add the appropriate tags to your post. – Paul Samsotha Nov 25 '18 at 07:06
  • I added description here: https://stackoverflow.com/questions/53450695/get-xml-in-plain-text – Peter Penzov Nov 25 '18 at 18:09
  • You already got a [comment](https://stackoverflow.com/questions/56265334/read-httpservletrequest-payload?noredirect=1#comment99152682_56265334) saying what you need to do: *You need to do this in a `Filter` and **wrap the incoming request**.* – Andreas May 23 '19 at 19:58
  • hm.... HttpLoggingFilter is filter? – Peter Penzov May 23 '19 at 19:59
  • Possible duplicate of [Logging HttpRequest parameters and request body](https://stackoverflow.com/q/6322362/5221149) – Andreas May 23 '19 at 20:02
  • Possible duplicate of [How to read request.getInputStream() multiple times](https://stackoverflow.com/q/4449096/5221149) – Andreas May 23 '19 at 20:03
  • Possible duplicate of [Http Servlet request lose params from POST body after read it once](https://stackoverflow.com/q/10210645/5221149) – Andreas May 23 '19 at 20:03
  • @Andreas Thanks! I will check them. One very important question: How I can call HttpLoggingFilter into the Java method handleMessage and get the request as body String? Probably I can make it service and Inject it? – Peter Penzov May 23 '19 at 20:09
  • @PeterPenzov You don't. Filter is standalone and doesn't interact with the controller method. If you **read the links** I provided, you'll learn how to write the filter to log the request without losing the request content. As for how to get the request content in the handler method, see [How to access plain json body in Spring rest controller?](https://stackoverflow.com/q/17866996/5221149). --- As you may have noticed by now, all your questions already have answers on here, so it might be good if you try to hone your searching skills. – Andreas May 23 '19 at 20:15
  • @Andreas I tried this: https://pastebin.com/5kLVJhg7. But I get UT010029: Stream is closed. Can you give me some advice how to implement it properly? – Peter Penzov May 23 '19 at 21:33
  • @PeterPenzov Why are you using `MultiReadHttpServletRequest` in your controller method? Doesn't do any good there. It's the filter that needs to establish the ability to read the request multiple times, since it is the first code that wants to do it. Did you even look at the links I provided? E.g. [this answer](https://stackoverflow.com/a/17129256/5221149) clearly shows `MultiReadHttpServletRequest` being **used by the Filter**. – Andreas May 23 '19 at 21:43
  • @Andreas Is it possible to read the HttpServletRequest content into the Java method handleMessage like the code example above? – Peter Penzov May 23 '19 at 21:52
  • If I read the content into the Filter I'm not aware how I can use thew code into the Java method handleMessage – Peter Penzov May 23 '19 at 21:54
  • Sorry, but since you seem unwilling to read the links I provided, that already answered all that, I'm done helping here. See [this comment](https://stackoverflow.com/questions/53461617/get-xml-from-httpservletrequest-and-use-into-endpoint?noredirect=1#comment99178342_53461617) for how to make the filter work. See [this comment](https://stackoverflow.com/questions/53461617/get-xml-from-httpservletrequest-and-use-into-endpoint?noredirect=1#comment99178598_53461617) for how to make the handler method work. – Andreas May 23 '19 at 21:56
  • Ok got it. I tried to use `HttpEntity httpEntity` as one of the answers but I get invalid response message for XML request!?!? Is it possible to use it? – Peter Penzov May 23 '19 at 22:28
  • see this link https://grokonez.com/spring-framework/spring-boot/create-springboot-xml-rest-service – sam May 24 '19 at 09:41
  • I don't see how this can help – Peter Penzov May 24 '19 at 11:58

2 Answers2

0

Here are a bunch of classes to do it. This is a once a OncePerRequestFilter implementation, check here https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/OncePerRequestFilter.html. Basically the problem is that in the chain filter, the request stream and response stream can be read just once. So, need to wrap these 2 streams inside something that can be read more than once.

In the first 2 lines I wrapped request and response inside requestToUse and responseToUse. ResettableStreamHttpServletRequest and ResettableStreamHttpServletResponse are wrapper classes that keeps the original string body inside of them, and every time the stream is needed they return a new stream.Then from there, you forget about request and response and start using requestToUse and responseToUse.

I took this from an old project I did. Actually there are more clases, but I extracted the main parts for you. This may not compile right away. Give it a try and let me know and I will help you to make it work.

    public class RequestResponseLoggingFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //here you wrap the request and response into some resetable istream class
        HttpServletRequest requestToUse = new ResettableStreamHttpServletRequest(request);
        HttpServletResponse responseToUse = new ResettableStreamHttpServletResponse(response);

        //you read the request to log it
        byte[] payload = IOUtils.toByteArray(requestToUse.getReader(), requestToUse.getCharacterEncoding());      
        String body =  new String(payload, requestToUse.getCharacterEncoding());

        //here you log the body request
        log.(body);

        //let the chain continue
        filterChain.doFilter(requestToUse, responseToUse);

        // Here we log the response
        String response =  new String(responseToUse.toString().getBytes(), responseToUse.getCharacterEncoding());

        //since you can read the stream just once, you will need it again for chain to be able to continue, so you reset it
        ResettableStreamHttpServletResponse responseWrapper = WebUtils.getNativeResponse(responseToUse, ResettableStreamHttpServletResponse.class);
        if (responseWrapper != null) {
            responseWrapper.copyBodyToResponse(true);
        }
    }

}

    public class ResettableStreamHttpServletRequest extends HttpServletRequestWrapper {

    private byte[] rawData;
    private ResettableServletInputStream servletStream;

    public ResettableStreamHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        rawData = IOUtils.toByteArray(request.getInputStream());
        servletStream = new ResettableServletInputStream();
        servletStream.setStream(new ByteArrayInputStream(rawData));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        servletStream.setStream(new ByteArrayInputStream(rawData));
        return servletStream;
    }

    @Override
    public BufferedReader getReader() throws IOException {
        servletStream.setStream(new ByteArrayInputStream(rawData));
        return new BufferedReader(new InputStreamReader(servletStream));
    }

}

    public class ResettableStreamHttpServletResponse extends HttpServletResponseWrapper {

    private ByteArrayServletOutputStream byteArrayServletOutputStream = new ByteArrayServletOutputStream();

    public ResettableStreamHttpServletResponse(HttpServletResponse response) throws IOException {
        super(response);
    }

    /**
     * Copy the cached body content to the response.
     *
     * @param complete whether to set a corresponding content length for the complete cached body content
     * @since 4.2
     */
    public void copyBodyToResponse(boolean complete) throws IOException {
        byte[] array = byteArrayServletOutputStream.toByteArray();
        if (array.length > 0) {
            HttpServletResponse rawResponse = (HttpServletResponse) getResponse();
            if (complete && !rawResponse.isCommitted()) {
                rawResponse.setContentLength(array.length);
            }
            rawResponse.getOutputStream().write(byteArrayServletOutputStream.toByteArray());
            if (complete) {
                super.flushBuffer();
            }
        }
    }

    /**
     * The default behavior of this method is to return getOutputStream() on the wrapped response object.
     */
    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return byteArrayServletOutputStream;
    }

    /**
     * The default behavior of this method is to return getOutputStream() on the wrapped response object.
     */
    @Override
    public String toString() {
        String response = new String(byteArrayServletOutputStream.toByteArray());
        return response;
    }

}
Perimosh
  • 2,304
  • 3
  • 20
  • 38
  • one question is not clear - How I can call this code into the Java method handleMessage and get the request as body String? Can you give me some advice how I can assess the code? – Peter Penzov May 24 '19 at 20:20
  • You don't call this logic from your hadleMessage method. This is a Servlet filter. Here you can learn about them https://javaee.github.io/servlet-spec/downloads/servlet-4.0/servlet-4_0_FINAL.pdf. Basically, in your Spring Boot app, in any @Configuration class, you define a filter bean like this: https://pastebin.com/BeH58kDR. Spring Boot will register your filter and will start intercepting all your request/response. – Perimosh May 25 '19 at 19:00
0

You dont need to do anything special here, Spring framework will do it for you. All you need is:

  1. Create a Pojo or Bean which represents your XML data.

  2. Add xml data format dependency to Gradle/Maven which will bind the request xml to your pojo.

     compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: '2.9.9'
    
  3. Tell your request handler to accept XML like this:

    @RequestMapping(value = "/xmlexample", method = RequestMethod.POST,consumes = "application/xml;charset=UTF-8") 
    public final boolean transactionHandler(@Valid @RequestBody Transaction transaction) {
        log.debug("Received transaction request with data {}", transaction);
        return true;
    }
    

And voila, you will have your transaction bean populated with your XML data.

CodeTripper
  • 336
  • 3
  • 11
  • The problem with this solution is that it doesn't scale. Your endpoints number may go wild with time. I have worked for an organization which had more than 20 apps, and for each app had maybe between 20-50 endpoint, depending on the kind of app. With this solution, if your pojo gets updated, you will need to update every place – Perimosh May 27 '19 at 20:38
  • @Perimosh Well, as usual it really depends. If you are working with springboot( read microservices), you would not likely have tons of endpoints and so wont need to update everywhere. And even if you needed to, it is not good design after all. Moreover at the end of the day, you would have to map the xml to a java class to make anything useful out of it, unless you are just dumping it to a sink. Anyways, it depends on what OPs requirement is. – CodeTripper May 28 '19 at 12:41
  • I don't agree at all. We started with a few endpoints. And then everything started to grow up really fast. Regardless if you are running monolinte apps or microservices, you need to handle this situation in a elegant, scalable and testable way. Throwing away a lot of "log.info" lines in the code it just doesn't feel good. My basic principle is, write code open for extension and closed for modifications. Having a filter to log all your endpoints make your life easy, and you don't need touch every place. Good conversation though! Thanks. – Perimosh May 28 '19 at 13:33