从HttpServletRequest获取POST请求体

202

我试图从HttpServletRequest对象中获取整个body。

我正在遵循的代码如下:

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();
}

我正在使用curl和wget进行功能测试,如下所示:

curl --header "MD5: abcd" -F "fileupload=@filename.txt http://localhost:8080/abcd.html"

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

然而 while ( (bytesRead = bufferedReader.read(charBuffer)) != -1 ) 没有返回任何内容,因此我在 StringBuffer 上没有添加任何内容。

11个回答

294

在Java 8中,您可以以更简单和清晰的方式完成它:

if ("POST".equalsIgnoreCase(request.getMethod())) 
{
   test = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
}

16
我更喜欢这个解决方案,因为它是纯Java编写的,没有任何第三方依赖。 - lu_ko
96
请记住,由于已经调用了 getReader,我们无法再读取请求正文。 - Nikhil Sahu
2
我们如何将新格式化的HTTP POST数据再次设置回请求中? - PAA
3
我尝试过这个解决方案,但它引入了一个非常严重的问题。我在oncePerRequest过滤器中使用了这段代码来记录请求信息,当我使用它时,所有我的post方法中的@modelAttribute绑定都会在对象的所有字段中给出null。我不建议使用这种方法。 - Mohammed Fathi
7
您需要创建一个WebMvcConfigurer并添加一个拦截器,在其中您可以使用HttpServletRequest requestCache = new ContentCachingRequestWrapper(request);,然后使用requestCache对象进行日志记录。 - subzero
我在搜索过程中找到了这个解决方案,它有效。https://www.jvt.me/posts/2020/05/25/read-servlet-request-body-multiple/ - codester

85

如果您能提供一个读取器输出的示例(即显示键或不显示键),我可以提供帮助。 - ArthurG
1
这对我有用。我可以确认这个解决方案也避免了一个常见的情况,即BufferedReader.readLine()无缘无故地“挂起”。 - Naren
2
嗨@DavidDomingo,它百分之百有效,我可以看到你在上面的回复中也有相同的评论(这也有效)。请检查您的代码中是否有问题(可能是过滤器),或者可能是因为Spring中的某个人没有调用getReader()方法,因为如果您调用两次或更多次,它只会返回第一个有效载荷。 - Dani
1
嗨@Dani,那就是为什么它不起作用。读取器是空的。我认为RestController在任何端点之前读取了它。获取正文的最简单方法是使用HttpEntity。 - David Domingo
1
你只能调用一次get reader函数。 - Mr_Hmp

36

请注意,你的代码相当嘈杂。 我知道这个帖子旧了,但是很多人仍然会阅读它。 你可以使用guava库来做同样的事情:

    if ("POST".equalsIgnoreCase(request.getMethod())) {
        test = CharStreams.toString(request.getReader());
    }

5
也许可以考虑这样写:if(RequestMethod.POST.name().equalsIgnoreCase(...)) { ... } - Madbreaks
5
我收到了 java.lang.IllegalStateException: getReader() has already been called for this request 的错误提示。 - PAA

22

如果你只想要POST请求的主体内容,你可以使用这样的方法:

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 "";
}

致谢:https://dev59.com/UHVC5IYBdhLWcg3wZwJT#5445161


1
请注意,request.getInputStream() 不像 request.getReader() 一样遵循请求字符编码。 不过,感谢提供链接。 - Vadzim
PUT方法的等效方式应该是什么? - devanathan

16

这适用于GET和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 "";
}

9

只有在不涉及文件上传的情况下才能起作用,就像curl示例一样,是吗? - Dave Newton
1
我尝试了这个东西。但是我仍然没有得到POST主体。我一定漏掉了什么。补充一下:我正在使用Tapestry进行开发。 - patel bhavin

5
这将适用于所有的HTTP方法。
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;
    }
}

5
我能想到的最简单的方法是:

request.getReader().lines().reduce("",String::concat)

然而,这将是一个需要解析的长字符串。如果您发送用户名为tim和密码为12345。上面代码的输出将如下所示:
{    "username":"tim",    "password": "12345"}

请注意

  • 请注意,使用reduce()方法时,我们正在执行可变缩减,它会进行大量的字符串复制,并且运行时间为O(N^2),其中N是字符数。如果您需要更高性能的结果,请查看可变缩减文档。

1
我是这样解决这个问题的。我创建了一个工具方法,它返回从请求体中提取的对象,使用ObjectMapper的readValue方法,该方法能够接收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;
}

2
PortalUtil是什么? - Ferry Kranenburg
我打赌这是来自 Liferay,Liferay 特定的 API。 - mvmn

0

我个人在开发服务器上使用此代码(而非生产环境)。看起来是有效的。主要困难在于一旦读取请求体,它就会丢失并且不传输到应用程序中。因此,您必须首先“缓存”它。

/* 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;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接