简体   繁体   English

从 HttpServletRequest 获取 POST 请求体

[英]Get the POST request body from HttpServletRequest

I am trying to get the whole body from the HttpServletRequest object.我正在尝试从 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 和 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"

But the while ( (bytesRead = bufferedReader.read(charBuffer)) != -1 ) does not return anything, and so I get nothing appended on StringBuffer.但是while ( (bytesRead = bufferedReader.read(charBuffer)) != -1 )没有返回任何东西,所以我没有在 StringBuffer 上附加任何东西。

In Java 8, you can do it in a simpler and clean way :在 Java 8 中,你可以用更简单和干净的方式来完成:

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

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:你可以使用guava库做同样的事情:

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

If all you want is the POST request body, you could use a method like this:如果你想要的只是 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 "";
}

Credit to: https://stackoverflow.com/a/5445161/1389219信用: https : //stackoverflow.com/a/5445161/1389219

This works for both GET and POST:这适用于 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 "";
}

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.例如,通过request.getParameter()getParameterValues()getParameterMap()调用。 Just remove the lines doing those calls from your code.只需从代码中删除执行这些调用的行。

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 .如果您发送用户名tim和密码12345 The output of the code above would look like this:上面代码的 output 看起来像这样:
{    "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.请注意,使用 reduce() 方法,我们正在执行可变归约,它会执行大量字符串复制,并且运行时间为 O(N^2),其中 N 是字符数。 Please check the Mutable Reduction documentation if you need a more performant result.如果您需要更高性能的结果,请查看Mutable Reduction文档。

This will work for all HTTP method.这将适用于所有 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;
    }
}

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.我创建了一个 util 方法,该方法返回从请求正文中提取的对象,使用能够接收读取器的 ObjectMapper 的 readValue 方法。

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

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

Implementing other solutions would possibly lead you to the following exception实施其他解决方案可能会导致您出现以下异常

java.lang.IllegalStateException: getInputStream() has already been called for this request

To read HttpServletRequest, following needs to be implemented.要读取 HttpServletRequest,需要执行以下操作。

Background:背景:

To get the request body from the request, HttpServletRequest is provided with and InputStream class. The getReader() is what is usually used to stream the request.为了从请求中获取请求主体,提供了 HttpServletRequest 和 InputStream class。getReader() 通常用于 stream 请求。 This function internally calls getInputStream() function, which returns us the stream for us to read the request.这个function内部调用了getInputStream() function,返回给我们stream供我们读取请求。 Now, note that its a stream, and can be opened only once.现在,请注意它是一个 stream,并且只能打开一次。 Hence, while reading this (ie implementing the solutions given in this thread) it usually throws "stream is already closed."因此,在阅读本文时(即实施此线程中给出的解决方案),它通常会抛出“流已关闭”。 exception.例外。 Now this happens because, the tomcat server has already opened and read the request once.现在发生这种情况是因为 tomcat 服务器已经打开并读取了一次请求。 Hence, we need to implement a wrapper here, which helps us to re-open an already read stream by keeping an instance of it.因此,我们需要在这里实现一个包装器,它可以帮助我们通过保留一个实例来重新打开一个已经读取过的 stream。 This wrapper again, cannot be used directly, instead, needs to be added at spring filter level, while the tomcat server is reading it.同样,这个包装器不能直接使用,而是需要在 spring 过滤器级别添加,而 tomcat 服务器正在读取它。

Code:代码:

Servlet Request Wrapper Class: Servlet 请求包装器 Class:

import lombok.extern.slf4j.Slf4j;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
@Slf4j
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final String body;public MyHttpServletRequestWrapper(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));

            char[] charBuffer = new char[128];
            int bytesRead = -1;

            while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                stringBuilder.append(charBuffer, 0, bytesRead);
            }
        } else {
            stringBuilder.append("");
        }
    } catch (IOException ex) {
        log.error("Error reading the request body...");
    } finally {
        if (bufferedReader != null) {
            try {
                bufferedReader.close();
            } catch (IOException ex) {
                log.error("Error closing bufferedReader...");
            }
        }
    }

    body = stringBuilder.toString();
}

@Override
public ServletInputStream getInputStream () {
    final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());

    ServletInputStream inputStream = new ServletInputStream() {
        @Override
        public boolean isFinished() {
            return false;
        }

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

        @Override
        public void setReadListener(ReadListener readListener) {

        }

        public int read () throws IOException {
            return byteArrayInputStream.read();
        }
    };

    return inputStream;
}

@Override
public BufferedReader getReader(){
    return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}

Now we need to use the wrapper my implementing it in a filter, as shown below.现在我们需要使用包装器在过滤器中实现它,如下所示。

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Objects;
@Component
@Order(1)
@Slf4j
public class ServletFilter implements Filter {

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if (servletRequest instanceof HttpServletRequest) {
            requestWrapper = new MyHttpServletRequestWrapper((HttpServletRequest) servletRequest);
        }
        if (Objects.isNull(requestWrapper)){
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            filterChain.doFilter(requestWrapper, servletResponse);
        }
}
}

Then, the suggested implementations can be used as follows to get the request body as following:然后,可以按如下方式使用建议的实现来获取请求正文,如下所示:

    private String getRequestBody(HttpServletRequest request) {
    try {
        return request.getReader().lines().collect(Collectors.joining());
    }catch (Exception e){
        e.printStackTrace();
        return "{}";
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM