How to decode the compressed body of a Gzip request in Spring MVC

I have a client that sends data using

CONTENT-ENCODING deflate 

I have code like this

 @RequestMapping(value = "/connect", method = RequestMethod.POST) @ResponseBody public Map onConnect(@RequestBody String body){} 

Currently, the body is printing out distorted compressed data. Is there a way to make Spring MVC automatically unzip it?

+7
source share
3 answers

You do not process it in Spring. Instead, you use a filter so that the data coming into Spring is already blown away.

We hope these two links help you get started.

+4
source

You will need to write your own filter to unzip the body of gzipped requests. Sine, you will read the entire input stream from the request, you also need to override the parameter parameterization method. This is the filter that I use in my code. It only supports gzip POST requests, but you can update it to use other types of requests if necessary. Also be careful to analyze the parameters that I use in the guava library, you can capture here: http://central.maven.org/maven2/com/google/guava/guava/

 public class GzipBodyDecompressFilter extends Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } /** * Analyzes servlet request for possible gzipped body. * When Content-Encoding header has "gzip" value and request method is POST we read all the * gzipped stream and is it haz any data unzip it. In case when gzip Content-Encoding header * specified but body is not actually in gzip format we will throw ZipException. * * @param servletRequest servlet request * @param servletResponse servlet response * @param chain filter chain * @throws IOException throws when fails * @throws ServletException thrown when fails */ @Override public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; boolean isGzipped = request.getHeader(HttpHeaders.CONTENT_ENCODING) != null && request.getHeader(HttpHeaders.CONTENT_ENCODING).contains("gzip"); boolean requestTypeSupported = HttpMethods.POST.equals(request.getMethod()); if (isGzipped && !requestTypeSupported) { throw new IllegalStateException(request.getMethod() + " is not supports gzipped body of parameters." + " Only POST requests are currently supported."); } if (isGzipped && requestTypeSupported) { request = new GzippedInputStreamWrapper((HttpServletRequest) servletRequest); } chain.doFilter(request, response); } /** * @inheritDoc */ @Override public final void destroy() { } /** * Wrapper class that detects if the request is gzipped and ungzipps it. */ final class GzippedInputStreamWrapper extends HttpServletRequestWrapper { /** * Default encoding that is used when post parameters are parsed. */ public static final String DEFAULT_ENCODING = "ISO-8859-1"; /** * Serialized bytes array that is a result of unzipping gzipped body. */ private byte[] bytes; /** * Constructs a request object wrapping the given request. * In case if Content-Encoding contains "gzip" we wrap the input stream into byte array * to original input stream has nothing in it but hew wrapped input stream always returns * reproducible ungzipped input stream. * * @param request request which input stream will be wrapped. * @throws java.io.IOException when input stream reqtieval failed. */ public GzippedInputStreamWrapper(final HttpServletRequest request) throws IOException { super(request); try { final InputStream in = new GZIPInputStream(request.getInputStream()); bytes = ByteStreams.toByteArray(in); } catch (EOFException e) { bytes = new byte[0]; } } /** * @return reproduceable input stream that is either equal to initial servlet input * stream(if it was not zipped) or returns unzipped input stream. * @throws IOException if fails. */ @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream sourceStream = new ByteArrayInputStream(bytes); return new ServletInputStream() { public int read() throws IOException { return sourceStream.read(); } public void close() throws IOException { super.close(); sourceStream.close(); } }; } /** * Need to override getParametersMap because we initially read the whole input stream and * servlet container won't have access to the input stream data. * * @return parsed parameters list. Parameters get parsed only when Content-Type * "application/x-www-form-urlencoded" is set. */ @Override public Map getParameterMap() { String contentEncodingHeader = getHeader(HttpHeaders.CONTENT_TYPE); if (!Strings.isNullOrEmpty(contentEncodingHeader) && contentEncodingHeader.contains("application/x-www-form-urlencoded")) { Map params = new HashMap(super.getParameterMap()); try { params.putAll(parseParams(new String(bytes))); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return params; } else { return super.getParameterMap(); } } /** * parses params from the byte input stream. * * @param body request body serialized to string. * @return parsed parameters map. * @throws UnsupportedEncodingException if encoding provided is not supported. */ private Map<String, String[]> parseParams(final String body) throws UnsupportedEncodingException { String characterEncoding = getCharacterEncoding(); if (null == characterEncoding) { characterEncoding = DEFAULT_ENCODING; } final Multimap<String, String> parameters = ArrayListMultimap.create(); for (String pair : body.split("&")) { if (Strings.isNullOrEmpty(pair)) { continue; } int idx = pair.indexOf("="); String key = null; if (idx > 0) { key = URLDecoder.decode(pair.substring(0, idx), characterEncoding); } else { key = pair; } String value = null; if (idx > 0 && pair.length() > idx + 1) { value = URLDecoder.decode(pair.substring(idx + 1), characterEncoding); } else { value = null; } parameters.put(key, value); } return Maps.transformValues(parameters.asMap(), new Function<Collection<String>, String[]>() { @Nullable @Override public String[] apply(final Collection<String> input) { return Iterables.toArray(input, String.class); } }); } } 

}

+14
source

This should be handled by the server, not the application.

As far as I know, Tomcat does not support it, although you could probably write a filter.

A common way to handle this is to place Tomcat (or any other Java container that you use) behind an Apache server that is configured to handle compressed request bodies.

+5
source

All Articles