2009-06-25 22 views
26

Wdrażam RESTful API w Grails i używam niestandardowego schematu uwierzytelniania, który obejmuje podpisanie treści żądania (w sposób podobny do schematu uwierzytelniania S3 Amazona). Dlatego, aby uwierzytelnić żądanie, muszę uzyskać dostęp do zawartości treści POST lub PUT treści surowej, aby obliczyć i zweryfikować podpis cyfrowy.Uzyskiwanie dostępu do surowej treści żądania PUT lub POST

Wykonuję uwierzytelnianie w przedInterceptorze w kontrolerze. Tak więc chcę, aby coś takiego jak request.body było dostępne w przechwytywaczu, i nadal można używać request.JSON w rzeczywistej akcji. Obawiam się, że jeśli odczytam ciało w przechwytywaczu za pomocą metody getInputStream lub getReader (metod dostarczonych przez ServletRequest), ciało pojawi się puste w akcji, gdy spróbuję uzyskać do niego dostęp za pomocą request.JSON.

Przeprowadzam się z Django do Grails, a ja miałem dokładnie ten sam numer w Django rok temu, ale został szybko załatany. Django dostarcza atrybut request.raw_post_data, którego możesz użyć do tego celu.

Wreszcie, aby być miłym i RESTful, chciałbym, żeby to działało dla żądań POST i PUT.

Każda rada lub wskazówki byłyby mile widziane. Jeśli nie istnieje, wolałbym wskazówki, jak wdrożyć eleganckie rozwiązanie zamiast pomysłów na szybkie i brudne włamania. =) W Django edytowałem niektóre moduły obsługi żądań oprogramowania pośredniego, aby dodać niektóre właściwości do żądania. Jestem nowy dla Groovy i Grails, więc nie mam pojęcia, gdzie ten kod się znajduje, ale nie miałbym nic przeciwko temu, jeśli to konieczne.

+0

Wszelkie powodem migracji z Django do Grails? – Divick

Odpowiedz

39

Jest to możliwe poprzez przesłonięcie HttpServletRequest w filtrze serwletu.

Trzeba wdrożyć HttpServletRequestWrapper który przechowuje ciało prośba: 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"); 
     } 
    } 

} 

filtr serwletu, który zastąpi obecną 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() { 
    } 
} 

Następnie trzeba uruchomić grails install-templates i edytować web.xml w src/templates/wojny i dodać po definicji 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> 

Następnie powinno być w stanie wywołać request.inputStream tak często, jak trzeba.

nie testowałem ten konkretny kod/procedurę, ale robiłem podobne rzeczy w przeszłości, więc powinien działać ;-)

Uwaga: należy pamiętać, że ogromne wnioski mogą zabić aplikacji (OutOfMemory. ..)

+1

Ciągle nie mogę sprawić, żeby to działało poprawnie. 1) Wiem, że filtry są poprawnie skonfigurowane, ponieważ w akcji "żądanie" zawiera ciąg znaków "[email protected]". 2) Próbowałem generowania żądania z obu wget --post-data = 'xxx' i curl -d @file i zweryfikowano, że żądanie jest poprawnie utworzone z obu przy użyciu Wireshark. 3) Jednak niezależnie od tego, co próbuję, request.reader.readLine() zwraca wartość null. 4) Dodałem także "bodyToString()", który wykorzystuje tablicę bajtów ciała, ale zawsze zwraca pusty ciąg znaków. Jakieś pomysły? –

+0

Zmieniono kod żądania multireadhttpservlet, aby działał bardziej niezawodnie. Możesz spróbować tego. –

+0

Świetne rozwiązanie! Pomógł mi, gdy złapałem błędy i wysłałem raport o błędzie. – Trick

25

Jak widać tutaj

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

tylko wyłączenie Grails automatyczną obsługę XML sprawia, że ​​tekst dostępny w sterownikach.Tak

class EventsController { 

static allowedMethods = [add:'POST'] 

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

Best, Anders

+3

Tak więc, aby wyjaśnić, po prostu próbowałem tego i działa, jednak ważne jest, aby nie importować Grails.converters. * (Co jest prawdopodobnie tym, co anders oznaczało przez "wyłączanie Grails'a automatyczną obsługą XML") – mikermcneil

+0

Właściwie miałem na myśli "obracając off grails Automatyczna obsługa XML 'jak w komentarzu Grajek http://jira.grails.org/browse/GRAILS-2017?focusedCommentId=46871&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-46871 –

1

Wydaje się, że jedynym sposobem, aby móc mieć stały dostęp zarówno do parametrów strumienia i prośba o żądania POST jest napisać otoki, który zastępuje odczyt strumienia jak również dostęp do parametrów. Tutaj jest doskonałym przykładem:

Modify HttpServletRequest body

Powiązane problemy