Acceder al cuerpo bruto de una solicitud PUT o POST


Estoy implementando una API RESTful en Grails y uso un esquema de autenticación personalizado que implica firmar el cuerpo de la solicitud (de manera similar al esquema de autenticación S3 de Amazon). Por lo tanto, para autenticar la solicitud, necesito acceder al contenido de POST o PUT body sin procesar para calcular y verificar la firma digital.

Estoy haciendo autenticación en un beforeInterceptor en el controlador. Así que quiero algo como una petición.cuerpo para ser accesible en el interceptor, y todavía ser capaz de utilizar la solicitud.JSON en la acción real. Me temo que si leo el cuerpo en el interceptor usando getInputStream o getReader (métodos proporcionados por ServletRequest), el cuerpo aparecerá vacío en la acción cuando intente acceder a él a través de request.JSON.

Estoy migrando de Django a Grails, y tuve exactamente el mismo problema en Django hace un año, pero se parcheó rápidamente. Django proporciona una solicitud.raw_post_data atributo que puede utilizar para este propósito.

Por último, para ser amable y RESTful, me gustaría que esto funcione para POST y PUT solicitudes.

Cualquier consejo o sugerencia sería muy apreciada. Si no existe, preferiría consejos sobre cómo implementar una solución elegante en lugar de ideas para hacks rápidos y sucios. = ) En Django, edité algunos manejadores de solicitudes de middleware para agregar algunas propiedades a la solicitud. Soy muy nuevo en Groovy y Grails, así que no tengo idea de dónde vive ese código, pero no me importaría hacer lo mismo si fuera necesario.

Author: Mickey Ristroph, 2009-06-26

3 answers

Es posible sobreescribiendo el HttpServletRequest en un filtro Servlet.

Necesita implementar un HttpServletRequestWrapper que almacene el cuerpo de la solicitud: 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");
        }
    }

}

Un filtro de Servlet que anula el ServletRequest actual: 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() {
    }
}

Entonces necesitas ejecutar grails install-templates y editar la web.xml en src / templates / war y añadir esto después del charEncodingFilter definición:

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

Entonces debería poder llamar a request.inputStream tan a menudo como lo necesite.

No he probado este código/procedimiento concreto, pero he hecho cosas similares en el pasado, por lo que debería funcionar; -)

Nota: tenga en cuenta que las solicitudes grandes pueden matar su aplicación (OutOfMemory...)

 40
Author: Siegfried Puchbauer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-29 17:28:47

Como se puede ver aquí

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

Simplemente desactivando grails el manejo automático de XML hace que el texto sea accesible en los controladores. Así

class EventsController {   

static allowedMethods = [add:'POST']

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

Mejor, Anders

 25
Author: anders.norgaard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-29 17:49:54

Parece que la única manera de poder tener acceso continuo tanto a los parámetros de flujo como de solicitud para las solicitudes POST es escribir un wrapper que anule la lectura de flujo, así como el acceso a los parámetros. Este es un gran ejemplo:

Modify HttpServletRequest body

 1
Author: sean,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:48