Grails 访问PUT或POST请求的原始主体

Grails 访问PUT或POST请求的原始主体,grails,servlets,Grails,Servlets,我在Grails中实现了一个RESTful API,并使用了一个自定义的身份验证方案,该方案涉及对请求体进行签名(以类似于Amazon的S3身份验证方案的方式)。因此,为了验证请求,我需要访问原始帖子或放置正文内容来计算和验证数字签名 我正在控制器中的beforeInterceptor中进行身份验证。所以我希望类似request.body的东西能够在拦截器中访问,并且仍然能够在实际操作中使用request.JSON。我担心如果我使用getInputStream或getReader(Servlet

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

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

我正在从Django迁移到Grails,一年前我在Django遇到了完全相同的问题,但很快就被修复了。Django提供了一个可用于此目的的request.raw\u post\u数据属性

最后,为了友好和安静,我希望这能用于POST和PUT请求


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

可以通过在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");
        }
    }

}
覆盖当前Servlet请求的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() {
    }
}
然后,您应该能够根据需要随时调用
request.inputStream

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

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

,如图所示

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

class EventsController {   

static allowedMethods = [add:'POST']

def add = {
    log.info("Got request " + request.reader.text)      
    render "OK"
}}
最好的,
Anders

看来,能够持续访问POST请求的流和请求参数的唯一方法是编写一个覆盖流读取和参数访问的包装器。下面是一个很好的例子:


我似乎仍然无法正常工作。1) 我知道过滤器设置正确,因为在一个操作中,“request”有一个字符串表示形式“grails.util.http”。MultiReadHttpServletRequest@43e1542f". 2) 我尝试使用wget--post data='xxx'和curl-d@file生成请求,并使用Wireshark验证了这两种格式的请求是否正确。3) 但是,无论我尝试什么,request.reader.readLine()都返回null。4) 我还添加了一个“bodyToString()”,它使用body字节数组,但它总是返回一个空字符串。有什么想法吗?我已经修改了multireadhttpservletrequest的代码,使其工作更可靠。你可能想试试。很好的解决方案!帮助我捕获错误并发送错误报告。它也不适用于我。我在getInputStream和getReader上都设置了一个断点,在主体中读取request.getParameter时,断点似乎没有命中,因此参数数组从未正确初始化。这是有意义的,因为HttpServletRequestWrapper将getParameter()委托给原始请求的getParameter(),它只知道原始的ServletInputStream(在包装器上第一次调用getInputStream()后,它将为空)。除了手工编写getParameter()及其朋友的重写之外,我似乎找不到任何解决方案。奇怪的是,似乎没有人在写博客或记录servlet过滤器时遇到过这种情况。除非我遗漏了什么……所以为了澄清,我只是尝试了这个方法,它是有效的,但是不导入grails.converters.*(这就是anders所谓的“关闭grails对XML的自动处理”的意思)实际上我的意思是“关闭grails对XML的自动处理”,就像Graemes评论中所说的那样。从Django迁移到grails有什么原因吗?
class EventsController {   

static allowedMethods = [add:'POST']

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