Get the POST request body from HttpServletRequest
Asked Answered
M

12

206

I am trying to get the whole body from the HttpServletRequest object.

The code I am following looks like this:

if ( request.getMethod().equals("POST") )
{
    StringBuffer sb = new StringBuffer();
    BufferedReader bufferedReader = null;
    String content = "";

    try {
        //InputStream inputStream = request.getInputStream();
        //inputStream.available();
        //if (inputStream != null) {
        bufferedReader =  request.getReader() ; //new BufferedReader(new InputStreamReader(inputStream));
        char[] charBuffer = new char[128];
        int bytesRead;
        while ( (bytesRead = bufferedReader.read(charBuffer)) != -1 ) {
            sb.append(charBuffer, 0, bytesRead);
        }
        //} else {
        //        sb.append("");
        //}

    } catch (IOException ex) {
        throw ex;
    } finally {
        if (bufferedReader != null) {
            try {
                bufferedReader.close();
            } catch (IOException ex) {
                throw ex;
            }
        }
    }

    test = sb.toString();
}

and I am testing the functionality with curl and wget as follows:

curl --header "MD5: abcd" -F "[email protected] http://localhost:8080/abcd.html"

wget --header="MD5: abcd" --post-data='{"imei":"351553012623446","hni":"310150","wdp":false}' http://localhost:8080/abcd.html"

But the while ( (bytesRead = bufferedReader.read(charBuffer)) != -1 ) does not return anything, and so I get nothing appended on StringBuffer.

Messidor answered 11/11, 2011 at 22:16 Comment(0)
E
302

In Java 8, you can do it in a simpler and clean way :

if ("POST".equalsIgnoreCase(request.getMethod())) 
{
   test = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
}
Eleaseeleatic answered 6/8, 2015 at 6:28 Comment(6)
I prefer this solution due to it's a pure Java without any 3rd party dependency.Shied
Though keep in mind that we cannot read the request body again as getReader has already been called.Receivable
How can we again set back the newly formatted HTTP POST data back into request?Mammillary
I have tried this solution but it introduced a very serious preblem , I used this code to log request info inside a oncePerRequest filter , and when i used it , all my @modelAttribute binding in all my post methods gave null in all the fields of an object .I don't recommend using this approach.Refractometer
@MohammedFathi you have to create a WebMvcConfigurer and add an Interceptor in which you can HttpServletRequest requestCache = new ContentCachingRequestWrapper(request); and then use the requestCache object for your logPennyweight
I found this solution during my search and it works jvt.me/posts/2020/05/25/read-servlet-request-body-multipleKneehole
H
86

Easy way with commons-io.

IOUtils.toString(request.getReader());

https://commons.apache.org/proper/commons-io/javadocs/api-2.5/org/apache/commons/io/IOUtils.html

Hendiadys answered 26/8, 2016 at 7:18 Comment(5)
I'd help if you could give out an example of how the output of the reader would look like (i.e. show keys or not)Shuler
This worked for me. I can confirm that this solution also avoids a common scenario where bufferedreader.readLine() "hangs" for no apparent reasonMiff
Hi @DavidDomingo, it works 100%, I can read that you are commenting the same in the response above this one (which works as well). Check that somewhere in your code (filters problably), or maybe because someone by Spring, the getReader() method is not called before, because if you call it twice or more times, it only returns the payload the first one.Hendiadys
Hi @Dani, that's why it doesn't work. The reader is empty. I think that the RestController reads it before you are able to do it in any endpoint. The easiest way to get the body is by using HttpEntity.Coffle
You can only call get reader onceBurdensome
B
36

Be aware, that your code is quite noisy. I know the thread is old, but a lot of people will read it anyway. You could do the same thing using the guava library with:

    if ("POST".equalsIgnoreCase(request.getMethod())) {
        test = CharStreams.toString(request.getReader());
    }
Briefless answered 18/1, 2013 at 16:10 Comment(2)
Perhaps consider if(RequestMethod.POST.name().equalsIgnoreCase(...)) { ... }Coolant
I get the java.lang.IllegalStateException: getReader() has already been called for this requestMammillary
C
22

If all you want is the POST request body, you could use a method like this:

static String extractPostRequestBody(HttpServletRequest request) throws IOException {
    if ("POST".equalsIgnoreCase(request.getMethod())) {
        Scanner s = new Scanner(request.getInputStream(), "UTF-8").useDelimiter("\\A");
        return s.hasNext() ? s.next() : "";
    }
    return "";
}

Credit to: https://mcmap.net/q/36074/-how-do-i-read-convert-an-inputstream-into-a-string-in-java

Crumpler answered 5/9, 2013 at 9:36 Comment(2)
Take into account that request.getInputStream() doesn't honor request character encoding as request.getReader() does. +1 for the link though.Gasiform
what should be the equivalent for PUT method?Submersible
P
16

This works for both GET and POST:

@Context
private HttpServletRequest httpRequest;


private void printRequest(HttpServletRequest httpRequest) {
    System.out.println(" \n\n Headers");

    Enumeration headerNames = httpRequest.getHeaderNames();
    while(headerNames.hasMoreElements()) {
        String headerName = (String)headerNames.nextElement();
        System.out.println(headerName + " = " + httpRequest.getHeader(headerName));
    }

    System.out.println("\n\nParameters");

    Enumeration params = httpRequest.getParameterNames();
    while(params.hasMoreElements()){
        String paramName = (String)params.nextElement();
        System.out.println(paramName + " = " + httpRequest.getParameter(paramName));
    }

    System.out.println("\n\n Row data");
    System.out.println(extractPostRequestBody(httpRequest));
}

static String extractPostRequestBody(HttpServletRequest request) {
    if ("POST".equalsIgnoreCase(request.getMethod())) {
        Scanner s = null;
        try {
            s = new Scanner(request.getInputStream(), "UTF-8").useDelimiter("\\A");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return s.hasNext() ? s.next() : "";
    }
    return "";
}
Preindicate answered 29/5, 2015 at 19:57 Comment(0)
N
9

If the request body is empty, then it simply means that it's already been consumed beforehand. For example, by a request.getParameter(), getParameterValues() or getParameterMap() call. Just remove the lines doing those calls from your code.

Nailbiting answered 11/11, 2011 at 23:45 Comment(2)
That only works if it's not a file upload, as the curl example is, no?Fibula
I tried this thing. But still I am not getting the POST body. I must be missing something. Just to add on : I am using Tapestry for the development.Messidor
D
6

This will work for all HTTP method.

public class HttpRequestWrapper extends HttpServletRequestWrapper {
    private final String body;

    public HttpRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        body = IOUtils.toString(request.getReader());
    }

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

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener listener) {
            }

        };
        return servletInputStream;
    }

    public String getBody() {
        return this.body;
    }
}
Doodle answered 15/10, 2018 at 4:22 Comment(0)
T
6

Easiest way I could think of:

request.getReader().lines().reduce("",String::concat)
  • However, this will be one long string which you will have to parse. IF you send a username of tim and a password of 12345. The output of the code above would look like this:
{    "username":"tim",    "password": "12345"}

Please be aware

  • Please be aware that with the reduce() method we are performing a Mutable Reduction which does a great deal of string copying and has a runtime of O(N^2) with N being the number of characters. Please check the Mutable Reduction documentation if you need a more performant result.
Trichromatism answered 26/3, 2022 at 15:12 Comment(0)
G
2

I resolved that situation in this way. I created a util method that return a object extracted from request body, using the readValue method of ObjectMapper that is capable of receiving a Reader.

public static <T> T getBody(ResourceRequest request, Class<T> class) {
    T objectFromBody = null;
    try {
        ObjectMapper objectMapper = new ObjectMapper();
        HttpServletRequest httpServletRequest = PortalUtil.getHttpServletRequest(request);
        objectFromBody = objectMapper.readValue(httpServletRequest.getReader(), class);
    } catch (IOException ex) {
        log.error("Error message", ex);
    }
    return objectFromBody;
}
Gollin answered 20/8, 2019 at 14:15 Comment(2)
what is PortalUtil?Wouldst
I bet this is from Liferay, Liferay-specific APILyra
S
1

You can get the request body from HttpServletRequest as follows:

ObjectMapper objectMapper = new ObjectMapper();
SomeRequest someRequest = objectMapper.readValue(request.getInputStream(),
                SomeRequest.class);

This is the most elegant and easy to understand solution I found.

Substandard answered 8/1 at 11:13 Comment(0)
E
0

I personnally use this code (on a dev server, not in production). Seems to work. The main difficulty is that once you read the request body, it will be lost and not transferred to the app. So you have to "cache" it first.

/* Export this filter as a jar and place it under directory ".../tomcat/lib" on your Tomcat server/
 In the lib directory, also place the dependencies you need
 (ex. org.apache.commons.io => commons-io-2.8.0.jar)
 
 Once this is done, in order to activate the filter, on the Tomcat server: 
 o in .../tomcat/conf/server.xml, add:
  <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t &quot;%r&quot;  [%{postdata}r] %s %b"/>
  => the server will log the "postdata" attribute we generate in the Java code.
 o in .../tomcat/conf/web.xml, add:
  <filter>
  <filter-name>post-data-dumper-filter</filter-name>
  <filter-class>filters.PostDataDumperFilter</filter-class>
  </filter>
  <filter-mapping>
  <filter-name>post-data-dumper-filter</filter-name>
  <url-pattern>/*</url-pattern>
  </filter-mapping>

Once you've done this, restart your tomcat server. You will get extra infos in file "localhost_access_log.<date>.txt"

*/

package filters;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.stream.Collectors;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.apache.commons.io.IOUtils;

class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
    private ByteArrayOutputStream cachedBytes;

    public MultiReadHttpServletRequest(HttpServletRequest request) {
        super(request);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (cachedBytes == null)
            cacheInputStream();

        return new CachedServletInputStream();
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    private void cacheInputStream() throws IOException {
        /* Cache the inputstream in order to read it multiple times.
         */
        cachedBytes = new ByteArrayOutputStream();
        IOUtils.copy(super.getInputStream(), cachedBytes);
    }

    /* An input stream which reads the cached request body */
    public class CachedServletInputStream extends ServletInputStream {
        private ByteArrayInputStream input;

        public CachedServletInputStream() {
            /* create a new input stream from the cached request body */
            input = new ByteArrayInputStream(cachedBytes.toByteArray());
        }
        //---------------------
        @Override
        public int read() throws IOException {
            return input.read();
        }

        @Override
        public boolean isFinished() {
            return input.available() == 0;
        }

        @Override
        public boolean isReady() {
            return true;
        }
        //---------------------
        @Override
        public void setReadListener(ReadListener arg0) {
            // TODO Auto-generated method stub
            // Ex. : throw new RuntimeException("Not implemented");
        }
    }
}

public final class PostDataDumperFilter implements Filter {

    private FilterConfig filterConfig = null;


    public void destroy() {
        this.filterConfig = null;
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (filterConfig == null)
            return;

        StringBuffer output = new StringBuffer();
        output.append("PostDataDumperFilter-");

        /* Wrap the request in order to be able to read its body multiple times */
        MultiReadHttpServletRequest multiReadRequest = new MultiReadHttpServletRequest((HttpServletRequest) request);

        // TODO : test the method in order not to log the body when receiving GET/DELETE requests ?
        // I finally leave it "as it", since I've seen GET requests containing bodies (hell...).
        output.append("Content-type=" + multiReadRequest.getContentType());
        output.append(" - HTTP Method=" + multiReadRequest.getMethod());
        output.append(" - REQUEST BODY = " + multiReadRequest.getReader().lines().collect(Collectors.joining(System.lineSeparator())));


        // Log the request parameters:
        Enumeration names = multiReadRequest.getParameterNames();
        if (names.hasMoreElements()) {
            output.append("- REQUEST PARAMS = ");
        }

        while (names.hasMoreElements()) {
            String name = (String) names.nextElement();
            output.append(name + "=");
            String values[] = multiReadRequest.getParameterValues(name);
            for (int i = 0; i < values.length; i++) {
                if (i > 0) output.append("' ");
                output.append(values[i]);
            }
            if (names.hasMoreElements()) output.append("&");
        }

        multiReadRequest.setAttribute("postdata", output);
        chain.doFilter(multiReadRequest, response);
    }

    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }
}
Earlap answered 7/5, 2021 at 10:6 Comment(0)
K
0

use (String) request.getAttribute("body")

Kalagher answered 28/11, 2023 at 7:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.