2009-06-25 24 views
26

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 del cuerpo crudo POST o PUT para calcular y verificar la firma digital.Accediendo al cuerpo sin procesar de una solicitud PUT o POST

Estoy haciendo autenticación en un beforeInterceptor en el controlador. Así que quiero que algo como request.body esté accesible en el interceptor, y aún así poder usar request.JSON en la acción real. Me temo que si leo el cuerpo en el interceptor utilizando 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 reparó rápidamente. Django proporciona un atributo request.raw_post_data que puede usar para este propósito.

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

Cualquier consejo o sugerencia sería muy apreciada. Si no existe, preferiría consejos sobre cómo implementar una solución elegante sobre 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.

+0

algún motivo para migrar de Django para Grails? – Divick

Respuesta

39

Es posible anulando HttpServletRequest en un filtro de servlet.

es necesario implementar un HttpServletRequestWrapper que almacena el cuerpo de la petición: src/java/griales/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 la ServletRequest actual: src/java/griales/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() { 
    } 
} 

Luego hay que ejecutar grails install-templates y editar el archivo web.xml en src/templates/guerra y añadir este después de la definición 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> 

Debería poder llamar al request.inputStream tantas veces como lo necesite.

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

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

+1

Todavía no puedo hacer que esto funcione correctamente. 1) Sé que los filtros están configurados correctamente, porque en una acción, "solicitud" tiene una cadena de "[email protected]". 2) Intenté generar la solicitud con wget --post-data = 'xxx' y curl -d @file, y verifiqué que la solicitud se formó correctamente con Wireshark. 3) Sin embargo, no importa lo que intento, request.reader.readLine() devuelve null. 4) También agregué un "bodyToString()" que usa la matriz de bytes del cuerpo, pero siempre devuelve una cadena vacía. ¿Alguna idea? –

+0

He cambiado el código de la multireadhttpservletrequest para que funcione de manera más confiable. Es posible que desee probar eso. –

+0

¡Gran solución! Me ayudó al detectar errores y enviar un informe de errores. – Trick

25

Como puede verse aquí

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

acaba de apagar griales manejo automático de XML hace que el texto accesible en los controladores.Al igual que este

class EventsController { 

static allowedMethods = [add:'POST'] 

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

mejor, Anders

+3

Así que para aclarar, yo sólo probamos este y funciona, sin embargo, es crucial que no se importan grails.converters. * (Que es lo que probablemente Anders entiende por 'apagar griales manejo automático de XML') – mikermcneil

+0

en realidad quería decir 'girando off grails manejo automático de XML 'como en Graemes comment http://jira.grails.org/browse/GRAILS-2017?focusedCommentId=46871&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-46871 –

1

Parece que la única manera de poder tener acceso continuo tanto a los parámetros de flujo y la solicitud de peticiones POST es escribir un envoltorio que anula la lectura corriente así como el acceso a los parámetros. Este es un gran ejemplo:

Modify HttpServletRequest body

Cuestiones relacionadas