【问题标题】:Accessing the raw body of a PUT or POST request访问 PUT 或 POST 请求的原始正文
【发布时间】:2010-11-06 00:18:05
【问题描述】:

我正在 Grails 中实现一个 RESTful API,并使用一个自定义身份验证方案,该方案涉及对请求正文进行签名(以类似于 Amazon 的 S3 身份验证方案的方式)。因此,要对请求进行身份验证,我需要访问原始 POST 或 PUT 正文内容来计算和验证数字签名。

我正在控制器的 beforeInterceptor 中进行身份验证。所以我希望在拦截器中可以访问 request.body 之类的东西,并且仍然能够在实际操作中使用 request.JSON。恐怕如果我使用 getInputStream 或 getReader(ServletRequest 提供的方法)读取拦截器中的正文,当我尝试通过 request.JSON 访问它时,正文将在操作中显示为空。

我正在从 Django 迁移到 Grails,一年前我在 Django 中遇到了完全相同的问题,但很快就修复了。 Django 提供了一个 request.raw_post_data 属性,您可以将其用于此目的。

最后,为了美观和 RESTful,我希望它适用于 POST 和 PUT 请求。

任何建议或指示将不胜感激。如果它不存在,我更喜欢关于如何实现优雅解决方案的指针,而不是快速和肮脏的黑客的想法。 =) 在 Django 中,我编辑了一些中间件请求处理程序以向请求添加一些属性。我对 Groovy 和 Grails 很陌生,所以我不知道该代码在哪里,但如果有必要我不介意这样做。

【问题讨论】:

  • 从 Django 迁移到 Grails 的任何原因?

标签: grails servlets


【解决方案1】:

可以通过覆盖 Servlet 过滤器中的 HttpServletRequest 来实现。

您需要实现一个存储请求正文的 HttpServletRequestWrapper: src/java/grails/util/http/MultiReadHttpServletRequest.java

package grails.util.http;

import org.apache.commons.io.IOUtils;

import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletInputStream;
import java.io.*;
import java.util.concurrent.atomic.AtomicBoolean;

public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {

    private byte[] body;

    public MultiReadHttpServletRequest(HttpServletRequest httpServletRequest) {
        super(httpServletRequest);
        // Read the request body and save it as a byte array
        InputStream is = super.getInputStream();
        body = IOUtils.toByteArray(is);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new ServletInputStreamImpl(new ByteArrayInputStream(body));
    }

    @Override
    public BufferedReader getReader() throws IOException {
        String enc = getCharacterEncoding();
        if(enc == null) enc = "UTF-8";
        return new BufferedReader(new InputStreamReader(getInputStream(), enc));
    }

    private class ServletInputStreamImpl extends ServletInputStream {

        private InputStream is;

        public ServletInputStreamImpl(InputStream is) {
            this.is = is;
        }

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

        public boolean markSupported() {
            return false;
        }

        public synchronized void mark(int i) {
            throw new RuntimeException(new IOException("mark/reset not supported"));
        }

        public synchronized void reset() throws IOException {
            throw new IOException("mark/reset not supported");
        }
    }

}

一个覆盖当前 servletRequest 的 Servlet 过滤器:src/java/grails/util/http/MultiReadServletFilter.java

package grails.util.http;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Set;
import java.util.TreeSet;

public class MultiReadServletFilter implements Filter {

    private static final Set<String> MULTI_READ_HTTP_METHODS = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER) {{
        // Enable Multi-Read for PUT and POST requests
            add("PUT");
            add("POST");
    }};

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        if(servletRequest instanceof HttpServletRequest) {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            // Check wether the current request needs to be able to support the body to be read multiple times
            if(MULTI_READ_HTTP_METHODS.contains(request.getMethod())) {
                // Override current HttpServletRequest with custom implementation
                filterChain.doFilter(new MultiReadHttpServletRequest(request), servletResponse);
                return;
            }
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    public void init(FilterConfig filterConfig) throws ServletException {
    }

    public void destroy() {
    }
}

然后你需要运行 grails install-templates 并编辑 src/templates/war 中的 web.xml 并将其添加到 charEncodingFilter 定义之后:

<filter>
    <filter-name>multireadFilter</filter-name>
    <filter-class>grails.util.http.MultiReadServletFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>multireadFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

您应该可以根据需要随时拨打request.inputStream

我没有测试过这个具体的代码/程序,但我过去做过类似的事情,所以它应该可以工作 ;-)

注意:请注意,巨大的请求可能会杀死您的应用程序(OutOfMemory...)

【讨论】:

  • 我似乎仍然无法正常工作。 1)我知道过滤器设置正确,因为在一个动作中,“请求”具有“grails.util.http.MultiReadHttpServletRequest@43e1542f”的字符串表示。 2) 我尝试使用 wget --post-data='xxx' 和 curl -d @file 生成请求,并验证请求是使用 Wireshark 正确形成的。 3) 但是,无论我尝试什么,request.reader.readLine() 都会返回 null。 4) 我还添加了一个使用正文字节数组的“bodyToString()”,但它总是返回一个空字符串。有什么想法吗?
  • 我更改了 multireadhttpservletrequest 的代码以使其工作更可靠。您可能想尝试一下。
  • 很好的解决方案!在捕获错误和发送错误报告时帮助我。
  • 它也不适合我。我在 getInputStream 和 getReader 都设置了一个断点,当 request.getParameter 在正文中读取时,它似乎没有被命中,因此参数数组永远不会被正确初始化。
  • 这是有道理的,因为 HttpServletRequestWrapper 将 getParameter() 委托给原始请求的 getParameter(),它只知道原始 ServletInputStream(在第一次调用 getInputStream() 后将为空)包装)。除了手写重写 getParameter() 及其朋友之外,我似乎找不到任何解决此问题的方法。奇怪的是,似乎没有人写博客或记录 servlet 过滤器。除非我错过了什么……
【解决方案2】:

这里可以看到

http://jira.codehaus.org/browse/GRAILS-2017

只需关闭 grails 对 XML 的自动处理,就可以在控制器中访问文本。像这样

class EventsController {   

static allowedMethods = [add:'POST']

def add = {
    log.info("Got request " + request.reader.text)      
    render "OK"
}}

最好, 安德斯

【讨论】:

  • 所以澄清一下,我只是尝试了这个并且它有效,但是你不要导入 grails.converters.* 至关重要(这可能是安德斯所说的'关闭 grails 自动处理 XML'的意思)
  • 我的意思是“关闭 grails 对 XML 的自动处理”,如 Graemes 评论 jira.grails.org/browse/…
【解决方案3】:

似乎能够继续访问 POST 请求的流和请求参数的唯一方法是编写一个覆盖流读取以及参数访问的包装器。这是一个很好的例子:

Modify HttpServletRequest body

【讨论】:

    猜你喜欢
    • 2015-10-19
    • 2018-11-27
    • 2015-11-20
    • 2020-07-19
    • 2015-12-02
    • 2017-08-10
    • 1970-01-01
    • 2017-11-02
    • 1970-01-01
    相关资源
    最近更新 更多