I am implementing a RESTful API in Grails, and use a custom authentication scheme that involves signing the body of the request (in a manner similar to Amazon\'s S3 authenti
As can be seen here
http://jira.codehaus.org/browse/GRAILS-2017
just turning off grails automatic handling of XML makes the text accessible in controllers. Like this
class EventsController {
static allowedMethods = [add:'POST']
def add = {
log.info("Got request " + request.reader.text)
render "OK"
}}
Best, Anders
It is possible by overriding the HttpServletRequest in a Servlet Filter.
You need to implement a HttpServletRequestWrapper that stores the request body: 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");
}
}
}
A Servlet Filter that overrides the current 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() {
}
}
Then you need to run grails install-templates and edit the web.xml in src/templates/war and add this after the charEncodingFilter definition:
<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>
You should then be able to call request.inputStream as often as you need.
I haven't tested this concrete code/procedure but I've done similar things in the past, so it should work ;-)
Note: be aware that huge requests can kill your application (OutOfMemory...)
It seems that the only way to be able to have continued access both to the stream and request parameters for POST requests is to write a wrapper that overrides the stream reading as well as the parameter access. Here is a great example:
Modify HttpServletRequest body