如何在Java中从HttpServletRequest中检索原始的POST数据

61

我正在尝试在Java中获取帖子数据。看起来这应该是最简单的事情之一,对吧?我的意思是,HttpServletRequest.getParameter必须做得对吧?那么你如何获取原始的帖子数据呢?

我发现了HttpServletRequest get JSON POST data并使用了Kdeveloper的代码从请求中提取了帖子数据。它有效,但有一个问题:我只能获取那个帖子数据一次

这是我从Kdeveloper的代码制作的方法:

public static String getPostData(HttpServletRequest req) {
    StringBuilder sb = new StringBuilder();
    try {
        BufferedReader reader = req.getReader();
        reader.mark(10000);

        String line;
        do {
            line = reader.readLine();
            sb.append(line).append("\n");
        } while (line != null);
        reader.reset();
        // do NOT close the reader here, or you won't be able to get the post data twice
    } catch(IOException e) {
        logger.warn("getPostData couldn't.. get the post data", e);  // This has happened if the request's reader is closed    
    }

    return sb.toString();
}

之前我在这个方法的末尾关闭了reader,但当该方法在同一请求上运行多次时,会导致异常。不关闭它就不会发生异常,但是该方法返回一个空字符串。

说实话,应该有一个公开的req.getPostData()方法 - 没有人认为这很有用吗?

那么我该如何编写此方法以始终返回正确的post数据?

3个回答

92

请求体可以通过 HttpServletRequest#getInputStream() 方法作为字节流获取:

InputStream body = request.getInputStream();
// ...

或者通过HttpServletRequest#getReader()作为字符流:

Reader body = request.getReader();
// ...

请注意,您只能读取一次。客户端不会多次重发相同的请求。调用 getParameter() 等方法也会隐式地读取它。如果您需要稍后分解参数,则必须将主体存储在某个位置并自行处理。


3
所以你的答案是,没有办法做我想做的事吗?这不是关于客户端发送多次。HttpServletRequest显然在某个内部位置存储了POST数据(因为您始终可以多次获取POST参数)。我感谢您的答案,我只是想完全理解您是否在说“这是不可能的”,还是只是重申我已经发现的东西。 - B T
14
在第一次调用getParameter()时,HttpServletRequest将内部使用getInputStream()读取和解析请求体(它是来自网络连接的字节流),并将其存储在一个映射中,您可以通过getParameterMap()获取该映射。此后,您无法再通过getInputStream()/getReader()读取请求体,因为它已经被读取。如果您需要更清楚地了解这个需求背后的功能要求,那么我们可能能够建议更好的实现方式。 - BalusC
9
你可以创建一个 HttpServletRequestWrapper,它包含一个 ByteArrayInputStream 来保存请求体的副本。 - BalusC
6
这是一个你正在寻找的请求包装器示例:https://dev59.com/IHNA5IYBdhLWcg3wNrEy - Tim Lewis
1
感谢您的回答。"只读一次" ... 我必须说,这个设计决定让我非常惊讶。 - Steven Wexler
显示剩余4条评论

7

我们遇到了IE强制使用text/plain方式提交的情况,因此我们必须使用getReader手动解析参数。该Servlet用于长轮询,因此在延迟后执行AsyncContext::dispatch时,它实际上是空手重发请求。

所以我只需在HttpServletRequest::setAttribute中存储首次出现的post即可。getReader方法会清空缓冲区,而getParameter方法也会清空缓冲区,但会自动存储参数。

    String input = null;

    // we have to store the string, which can only be read one time, because when the
    // servlet awakens an AsyncContext, it reposts the request and returns here empty handed
    if ((input = (String) request.getAttribute("com.xp.input")) == null) {
        StringBuilder buffer = new StringBuilder();
        BufferedReader reader = request.getReader();

        String line;
        while((line = reader.readLine()) != null){
            buffer.append(line);
        }
        // reqBytes = buffer.toString().getBytes();

        input = buffer.toString();
        request.setAttribute("com.xp.input", input);
    }

    if (input == null) {
        response.setContentType("text/plain");
        PrintWriter out = response.getWriter();
        out.print("{\"act\":\"fail\",\"msg\":\"invalid\"}");
    }       

0
这对我有用:(请注意需要Java 8)
String requestData = request.getReader().lines().collect(Collectors.joining());
UserJsonParser u = gson.fromJson(requestData, UserJsonParser.class);

UserJsonParse是一个类,它展示了gson如何解析json格式。
类的定义如下:
public class UserJsonParser {

    private String username;
    private String name;
    private String lastname;
    private String mail;
    private String pass1;
//then put setters and getters
}

被解析的JSON字符串如下:

$jsonData: {    "username": "testuser",    "pass1": "clave1234" }

其余的值(邮件、姓氏和名字)被设置为null


我认为这个答案与所问的问题无关。 - eis

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