Accessing the raw body of a PUT or POST request

前端 未结 3 1857
鱼传尺愫
鱼传尺愫 2020-11-28 23:43

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 authenti

相关标签:
3条回答
  • 2020-11-29 00:26

    As can be seen here

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

    just turning off grails automatic handling of XML makes the text accessible in controllers. Like this

    class EventsController {   
    
    static allowedMethods = [add:'POST']
    
    def add = {
        log.info("Got request " + request.reader.text)      
        render "OK"
    }}
    

    Best, Anders

    0 讨论(0)
  • 2020-11-29 00:32

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

    0 讨论(0)
  • 2020-11-29 00:41

    It seems that the only way to be able to have continued access both to the stream and request parameters for POST requests is to write a wrapper that overrides the stream reading as well as the parameter access. Here is a great example:

    Modify HttpServletRequest body

    0 讨论(0)
提交回复
热议问题