簡體   English   中英

從 HttpServletRequest 獲取 POST 請求體

[英]Get the POST request body from HttpServletRequest

我正在嘗試從 HttpServletRequest object 獲取整個主體。

我正在關注的代碼如下所示:

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 上附加任何東西。

在 Java 8 中,你可以用更簡單和干凈的方式來完成:

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

使用 commons-io 的簡單方法。

IOUtils.toString(request.getReader());

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

請注意,您的代碼非常嘈雜。 我知道這個線程很舊,但無論如何還是有很多人會閱讀它。 你可以使用guava庫做同樣的事情:

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

如果你想要的只是 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 : //stackoverflow.com/a/5445161/1389219

這適用於 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 "";
}

如果請求體是空的,那么它只是意味着它已經被預先消耗了。 例如,通過request.getParameter()getParameterValues()getParameterMap()調用。 只需從代碼中刪除執行這些調用的行。

我能想到的最簡單的方法:

request.getReader().lines().reduce("",String::concat)
  • 但是,這將是一個您必須解析的長字符串。 如果您發送用戶名tim和密碼12345 上面代碼的 output 看起來像這樣:
{    "username":"tim",    "password": "12345"}

請注意

  • 請注意,使用 reduce() 方法,我們正在執行可變歸約,它會執行大量字符串復制,並且運行時間為 O(N^2),其中 N 是字符數。 如果您需要更高性能的結果,請查看Mutable Reduction文檔。

這將適用於所有 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;
    }
}

我就是這樣解決了那個情況的。 我創建了一個 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;
}

我個人使用此代碼(在開發服務器上,而不是在生產中)。 似乎工作。 主要的難點在於,一旦你讀取了請求正文,它就會丟失並且不會傳輸到應用程序中。 所以你必須先“緩存”它。

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

實施其他解決方案可能會導致您出現以下異常

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

要讀取 HttpServletRequest,需要執行以下操作。

背景:

為了從請求中獲取請求主體,提供了 HttpServletRequest 和 InputStream class。getReader() 通常用於 stream 請求。 這個function內部調用了getInputStream() function,返回給我們stream供我們讀取請求。 現在,請注意它是一個 stream,並且只能打開一次。 因此,在閱讀本文時(即實施此線程中給出的解決方案),它通常會拋出“流已關閉”。 例外。 現在發生這種情況是因為 tomcat 服務器已經打開並讀取了一次請求。 因此,我們需要在這里實現一個包裝器,它可以幫助我們通過保留一個實例來重新打開一個已經讀取過的 stream。 同樣,這個包裝器不能直接使用,而是需要在 spring 過濾器級別添加,而 tomcat 服務器正在讀取它。

代碼:

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

現在我們需要使用包裝器在過濾器中實現它,如下所示。

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

然后,可以按如下方式使用建議的實現來獲取請求正文,如下所示:

    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