Доступ к необработанному телу запроса PUT или POST - PullRequest
27 голосов
/ 26 июня 2009

Я реализую RESTful API в Grails и использую собственную схему аутентификации, которая включает в себя подписание тела запроса (аналогично схеме аутентификации Amazon S3). Поэтому для проверки подлинности запроса мне нужно получить доступ к необработанному содержанию тела POST или PUT, чтобы вычислить и проверить цифровую подпись.

Я делаю аутентификацию в beforeInterceptor в контроллере. Поэтому я хочу, чтобы что-то вроде request.body было доступно в перехватчике, и все же иметь возможность использовать request.JSON в действии. Боюсь, что если я прочитаю тело в перехватчике с помощью getInputStream или getReader (методы, предоставляемые ServletRequest), тело будет пустым в действии, когда я пытаюсь получить к нему доступ через request.JSON.

Я мигрирую из Джанго в Граальс, и у меня была точно такая же проблема в Джанго год назад, но она была быстро исправлена. Django предоставляет атрибут request.raw_post_data, который вы можете использовать для этой цели.

Наконец, чтобы быть красивым и RESTful, я бы хотел, чтобы это работало для запросов POST и PUT.

Любой совет или указатели будут с благодарностью. Если бы его не было, я бы предпочел указатели о том, как реализовать элегантное решение, а не идеи для быстрого и грязного взлома. =) В Django я отредактировал некоторые обработчики запросов промежуточного программного обеспечения, чтобы добавить некоторые свойства в запрос. Я очень плохо знаком с Groovy и Grails, поэтому понятия не имею, где находится этот код, но я бы не стал делать то же самое в случае необходимости.

Ответы [ 3 ]

42 голосов
/ 26 июня 2009

Это возможно путем переопределения запроса HttpServletRequest в фильтре сервлетов.

Вам необходимо реализовать HttpServletRequestWrapper, который хранит тело запроса: 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");
        }
    }

}

Фильтр сервлета, который переопределяет текущий запрос servlet: 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() {
    }
}

Затем вам нужно запустить grails install-templates и отредактировать файл web.xml в src / templates / war и добавить его после определения 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>

После этого вы сможете звонить request.inputStream так часто, как вам нужно.

Я не тестировал этот конкретный код / ​​процедуру, но я делал подобные вещи в прошлом, поэтому он должен работать; -)

Примечание: помните, что большие запросы могут убить ваше приложение (OutOfMemory ...)

26 голосов
/ 29 октября 2009

Как видно тут

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

Просто отключив Grails, автоматическая обработка XML делает текст доступным в контроллерах. Как это

class EventsController {   

static allowedMethods = [add:'POST']

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

Лучшее, Андерс

1 голос
/ 22 июня 2011

Кажется, что единственный способ иметь постоянный доступ как к потоку, так и к параметрам запроса для запросов POST - это написать оболочку, которая переопределяет чтение потока, а также доступ к параметрам. Вот отличный пример:

Изменение тела HttpServletRequest

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...