web-dev-qa-db-fra.com

Accès au corps brut d'un PUT ou d'une demande POST

J'implémente une API RESTful dans Grails et j'utilise un schéma d'authentification personnalisé qui implique la signature du corps de la demande (d'une manière similaire au schéma d'authentification S3 d'Amazon). Par conséquent, pour authentifier la demande, j'ai besoin d'accéder au contenu brut POST ou PUT body pour calculer et vérifier la signature numérique.

Je fais l'authentification dans un beforeInterceptor dans le contrôleur. Je veux donc que quelque chose comme request.body soit accessible dans l'intercepteur, et puisse toujours utiliser request.JSON dans l'action réelle. J'ai peur que si je lis le corps dans l'intercepteur en utilisant getInputStream ou getReader (méthodes fournies par ServletRequest), le corps apparaîtra vide dans l'action lorsque j'essayer d'y accéder via request.JSON.

Je migre de Django vers Grails, et j'ai eu exactement le même problème dans Django il y a un an, mais il a été rapidement corrigé. Django fournit un attribut request.raw_post_data que vous pouvez utiliser à cet effet.

Enfin, pour être agréable et reposant, j'aimerais que cela fonctionne pour POST et les demandes PUT.

Tout conseil ou pointage serait grandement apprécié. S'il n'existe pas, je préférerais des conseils sur la façon de mettre en œuvre une solution élégante plutôt que des idées de hacks rapides et sales. =) Dans Django, j'ai édité des gestionnaires de requêtes middleware pour ajouter des propriétés à la requête. Je suis très nouveau sur Groovy et Grails, donc je n'ai aucune idée de l'endroit où se trouve ce code, mais cela ne me dérangerait pas de faire la même chose si nécessaire.

27
Mickey Ristroph

Cela est possible en remplaçant HttpServletRequest dans un filtre de servlet.

Vous devez implémenter un HttpServletRequestWrapper qui stocke le corps de la demande: 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 filtre de servlet qui remplace la servletRequest actuelle: 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() {
    }
}

Ensuite, vous devez exécuter grails install-templates et éditez le web.xml dans src/templates/war et ajoutez ceci après la définition 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>

Vous devriez alors pouvoir appeler request.inputStream aussi souvent que nécessaire.

Je n'ai pas testé ce code/cette procédure concrète mais j'ai fait des choses similaires dans le passé, donc ça devrait marcher ;-)

Remarque: sachez que des demandes énormes peuvent tuer votre application (OutOfMemory ...)

43

Comme on peut le voir ici

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

la désactivation de la gestion automatique de XML par Grails rend le texte accessible dans les contrôleurs. Comme ça

class EventsController {   

static allowedMethods = [add:'POST']

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

Meilleur, Anders

26
anders.norgaard

Il semble que la seule façon d'avoir un accès continu à la fois au flux et aux paramètres de requête pour les requêtes POST est d'écrire un wrapper qui écrase la lecture du flux ainsi que l'accès aux paramètres. Ici est un excellent exemple:

Modifier le corps HttpServletRequest

1
sean