Доступ к необработанному телу запроса PUT или POST
Я реализую 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 ответа
Это возможно путем переопределения запроса 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");
}
}
}
Фильтр сервлетов, который переопределяет текущий запрос 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() {
}
}
Тогда вам нужно бежать 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...)
Как можно увидеть здесь
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"
}}
Бест, Андерс
Кажется, что единственный способ иметь постоянный доступ как к потоку, так и к параметрам запроса для запросов POST - это написать оболочку, которая переопределяет чтение потока, а также доступ к параметрам. Вот отличный пример: