访问 PUT 或 POST 请求的原始正文 [英] Accessing the raw body of a PUT or POST request

查看:27
本文介绍了访问 PUT 或 POST 请求的原始正文的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am implementing a RESTful API in Grails, and use a custom authentication scheme that involves signing the body of the request (in a manner similar to Amazon's S3 authentication scheme). Therefore, to authenticate the request, I need to access the raw POST or PUT body content to calculate and verify the digital signature.

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

I am doing authentication in a beforeInterceptor in the controller. So I want something like request.body to be accessible in the interceptor, and still be able to use request.JSON in the actual action. I am afraid if I read the body in the interceptor using getInputStream or getReader (methods provided by ServletRequest), the body will appear empty in the action when I try to access it via request.JSON.

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

I am migrating from Django to Grails, and I had the exact same issue in Django a year ago, but it was quickly patched. Django provides a request.raw_post_data attribute you can use for this purpose.

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

Lastly, to be nice and RESTful, I'd like this to work for POST and PUT requests.

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

Any advice or pointers would be greatly appreciated. If it doesn't exist, I'd prefer pointers on how to implement an elegant solution over ideas for quick and dirty hacks. =) In Django, I edited some middleware request handlers to add some properties to the request. I am very new to Groovy and Grails, so I have no idea where that code lives, but I wouldn't mind doing the same if necessary.

推荐答案

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

It is possible by overriding the HttpServletRequest in a Servlet Filter.

你需要实现一个 HttpServletRequestWrapper 来存储请求体:src/java/grails/util/http/MultiReadHttpServletRequest.java

You need to implement a HttpServletRequestWrapper that stores the request body: 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

A Servlet Filter that overrides the current servletRequest: 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 定义之后:

Then you need to run grails install-templates and edit the web.xml in src/templates/war and add this after the charEncodingFilter definition:

<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.

You should then be able to call request.inputStream as often as you need.

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

I haven't tested this concrete code/procedure but I've done similar things in the past, so it should work ;-)

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

Note: be aware that huge requests can kill your application (OutOfMemory...)

这篇关于访问 PUT 或 POST 请求的原始正文的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆