14

我有一个发送数据的客户端

CONTENT-ENCODING deflate

我有这样的代码

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

目前'body'打印出乱码的压缩数据。有什么办法可以让 Spring MVC 自动解压?

4

3 回答 3

30

您需要编写自己的过滤器来解压缩 gzip 请求的主体。因为您将从请求中读取整个输入流,您也需要覆盖参数解析方法。这是我在代码中使用的过滤器。仅支持压缩后的 POST 请求,但您可以根据需要对其进行更新以使用其他类型的请求。还要注意解析我正在使用 guava 库的参数,你可以从这里获取你的: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);
                        }
                    });
        }
    }
}
于 2014-10-06T23:09:41.130 回答
7

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

As far as I know, Tomcat doesn't support it, though you could probably write a filter.

A common way to handle this is to put Tomcat ( or whatever Java container you're using) behind an Apache server that is configured to handle compressed request bodies.

于 2013-05-19T19:19:48.420 回答
2

你不处理它在春天。相反,您使用过滤器,以便数据到达 Spring 时已经放气。

希望这两个链接可以帮助您入门。

于 2013-05-19T19:13:27.280 回答