2009-06-25 8 views
26

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

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 que je puisse toujours utiliser request.JSON dans l'action actuelle. 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 quand j'essayerai 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 à cette fin. Enfin, pour être agréable et RESTful, je voudrais que cela fonctionne pour les demandes POST et PUT.

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

+0

Quel est le motif de migration de Django à Grails? – Divick

Répondre

39

Il est possible de surcharger HttpServletRequest dans un filtre de servlet.

Vous devez mettre en œuvre un HttpServletRequestWrapper qui stocke le corps de la requête: 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 Servlet qui remplace le ServletRequest actuel: 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 modifier les web.xml dans src/templates/guerre et l'ajouter après la définition de 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 vous le souhaitez.

Je ne l'ai pas testé ce code/procédure concrète mais j'ai fait des choses semblables dans le passé, il devrait donc fonctionner ;-)

Note: être conscient que d'énormes demandes peuvent tuer votre application (OutOfMemory. ..)

+1

Je n'arrive toujours pas à le faire fonctionner correctement. 1) Je sais que les filtres sont configurés correctement, car dans une action, "request" a une représentation sous forme de chaîne de caractères "[email protected]". 2) J'ai essayé de générer la requête avec à la fois wget --post-data = 'xxx' et curl -d @file, et vérifié que la requête est correctement formée à partir des deux en utilisant Wireshark. 3) Cependant, quoi que j'essaie, request.reader.readLine() renvoie null. 4) J'ai également ajouté un "bodyToString()" qui utilise le tableau d'octets du corps, mais il renvoie toujours une chaîne vide. Des idées? –

+0

J'ai changé le code de la demande multireadhttpservlet pour travailler plus fiable. Vous pourriez vouloir essayer cela. –

+0

Excellente solution! A m'a aidé en attrapant des erreurs et en envoyant le rapport d'erreur. – Trick

25

Comme on le voit ici

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

simplement éteindre Grails la gestion automatique des XML rend le texte accessible dans les contrôleurs.Comme cette

class EventsController { 

static allowedMethods = [add:'POST'] 

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

Best, Anders

+3

Donc, pour clarifier, je viens d'essayer ceci et cela fonctionne, mais il est crucial que vous n'importiez pas grails.converters. * (Ce que signifiait probablement "désactiver la gestion automatique des grails de XML") – mikermcneil

+0

off grails manipulation automatique de XML »comme dans Graemes commentaire http://jira.grails.org/browse/GRAILS-2017?focusedCommentId=46871&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-46871 –

1

Il semble que la seule façon d'être en mesure d'avoir un accès continu à la fois aux paramètres de flux et demande pour les requêtes POST est d'écrire un wrapper qui remplace la lecture de flux ainsi que l'accès aux paramètres. Voici un exemple:

Modify HttpServletRequest body

Questions connexes