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

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

问题描述

我正在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非常陌生,所以我不知道代码的存在位置,但我不介意在必要的情况下做同样的事情。

解决方案你可能需要实现一个存储请求体的HttpServletRequestWrapper:
src

/java/grails/util/http/MultiReadHttpServletRequest.java

  package grails.util.http; 

导入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);
//读取请求体并将其保存为字节数组$ b $ InputStream is = super.getInputStream();
body = IOUtils.toByteArray(is);

$ b @Override
public ServletInputStream getInputStream()throws IOException {
返回新的ServletInputStreamImpl(new ByteArrayInputStream(body));
}

@Override
public BufferedReader getReader()throws IOException {
String enc = getCharacterEncoding();
if(enc == null)enc =UTF-8;
返回新的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);



$ b

一个Servlet过滤器覆盖当前的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){{
//为PUT和POST请求启用多重读取
add(PUT);
add(POST);
}};
$ b $ public void doFilter(ServletRequest servletRequest,ServletResponse servletResponse,FilterChain filterChain)throws IOException,ServletException {$ b $ if(servletRequest instanceof HttpServletRequest){
HttpServletRequest request =(HttpServletRequest)servletRequest;
//检查当前请求是否需要支持多次读取正文
if(MULTI_READ_HTTP_METHODS.contains(request.getMethod())){
// Override current HttpServletRequest与自定义实现
filterChain.doFilter(new MultiReadHttpServletRequest(request),servletResponse);
return;
}
}
filterChain.doFilter(servletRequest,servletResponse);

$ b $ 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 ...)


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.

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.

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.

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

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.

解决方案

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

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

}

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

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>

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

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

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

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