Java Web 减少网络 IO、静态资源磁盘 IO 有效的办法--响应使用 GZIP( 压缩http请求与响应gzip压缩)
(转载http://blog.csdn.net/hylclxy/article/details/7779662)
出于节约流量考虑, 客户端在向服务端发送request的时候对post数据进行gzip压缩, 同时服务端把返回的数据也进行gzip压缩. 为防止遗忘, 记录在此.
编写工具类GzipUtil.java, 开始没考虑好, 方法实现得较乱:
-
public static String METHOD_POST = "POST"; public static final String ACCEPT_ENCODING = "Accept-Encoding"; public static final String CONTENT_ENCOING = "Content-Encoding"; public static final String CONTENT_LENGTH = "Content-Length"; public static final String ENCODING_GZIP = "gzip"; public static final String MIME_APPLICATION_X_GZIP = "application/x-gzip"; public static final String ENCODING = "UTF-8"; /** * 对参数进行gzip压缩操作, 返回字节数组 * @param data 待压缩的字节数组 * @return 压缩后的gzip字节数组 * @throws IOException */ public static byte[] gzip(byte[] data) throws IOException{ ByteArrayOutputStream baos = new ByteArrayOutputStream(); GZIPOutputStream gos = new GZIPOutputStream(baos); gos.write(data); gos.finish(); gos.flush(); byte[] result = baos.toByteArray(); baos.flush(); try{ gos.close(); }catch(IOException e){ logger.warn("Close GZIPOutputStream fail:", e); } return result; } /** * 对参数进行gzip压缩操作, 结果输出到参数的输出流中 * @param data 待压缩的字节数组 * @param os 压缩后的输出流 * @throws IOException */ public static void gzip(byte[] data, OutputStream os) throws IOException{ GZIPOutputStream gos = new GZIPOutputStream(os); gos.write(data); gos.finish(); gos.flush(); try{ gos.close(); }catch(IOException e){ logger.warn("Close GZIPOutputStream fail:", e); } } /** * 对输入流进行gzip压缩 * @param ins 待压缩的输入流 * @param os 压缩后的输出流 * @throws IOException */ public static void gzip(InputStream ins, OutputStream os) throws IOException{ GZIPOutputStream gos = new GZIPOutputStream(os); int b; while((b = ins.read()) != -1){ gos.write(b); } gos.finish(); gos.flush(); } /** * 解压缩 * @param ins 输入流 * @return 解压完的数据字节数组 * @throws IOException */ public static byte[] unGzip(InputStream ins) throws IOException{ if(logger.isInfoEnabled()){ logger.info("Start to ungzip parameters....."); } GZIPInputStream gis = new GZIPInputStream(ins); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int b; while((b = gis.read()) != -1){ baos.write(b); } byte[] result = baos.toByteArray(); if(logger.isInfoEnabled()){ logger.info("Ungzip parameters bytes OK, result bytes size is: " + result.length); } try{ gis.close(); }catch(IOException e){ logger.warn("Close GZIPInputStream fail:", e); } return result; } /** * 对输入流的数据解压缩 * @param ins 待解压的输入流 * @param os 解压后的输出流 * @throws IOException */ public static void unGzip(InputStream ins, OutputStream os) throws IOException{ GZIPInputStream gis = new GZIPInputStream(ins); int b; while((b = gis.read()) != -1){ os.write(b); } try{ gis.close(); }catch(IOException e){ logger.warn("Close GZIPInputStream fail:", e); } } /** * 把从gzip流中解压出来的参数和request原有的参数合并成一个新Map返回 * * 客户端传来的数据没有经过url编码, 不需要解码 * @param data 解压后的字节数组 * @param res 请求的request * @return 完整的parameterMap * @throws UnsupportedEncodingException */ public static Map<String, Object> getParameterMap(byte[] data, ServletRequest request) throws UnsupportedEncodingException{ String body = null; if(data != null && data.length > 0){ body = new String(data, ENCODING); } if(body != null && !"".equals(body.trim())){ if(logger.isInfoEnabled()){ logger.info("Ungzip parameters string is : " + body); } //新parameterMap Map<String, Object> paramsMap = new LinkedHashMap<String, Object>(); //把原有parameterMap添加到新map中 if(request.getParameterMap() != null){ paramsMap.putAll(request.getParameterMap()); } String splitMapFlag = "&"; String spiltKeyValueFlag = "="; String[] mapArr = body.split(splitMapFlag); String[] arr = null; for(String tagValue : mapArr){ arr = tagValue.split(spiltKeyValueFlag, 2); if(arr.length == 2){ if(paramsMap.containsKey(arr[0]) && paramsMap.get(arr[0]) != null){ // List<String> list = new ArrayList<String>(); // list.addAll(Arrays.asList((String[])paramsMap.get(arr[0]))); // list.add(arr[1]); // paramsMap.put(arr[0], list.toArray(new String[list.size()])); // String[] oldArr = (String[])paramsMap.get(arr[0]); // String[] newArr = new String[oldArr.length + 1]; // System.arraycopy(oldArr, 0, newArr, 0, oldArr.length); // newArr[newArr.length - 1] = arr[1]; // paramsMap.put(arr[0], newArr); String[] array = (String[])paramsMap.get(arr[0]);; array = Arrays.copyOf(array, array.length + 1); array[array.length - 1] = arr[1]; paramsMap.put(arr[0], array); }else{ paramsMap.put(arr[0], new String[]{arr[1]}); } } } return paramsMap; } return request.getParameterMap(); }
修改filter类, 同时兼容不支持gzip压缩的老版本客户端:
-
//判断返回给客户端的响应流是否是需要经过gzip压缩 String acceptEncoding = req.getHeader(GZipUtil.ACCEPT_ENCODING); if (acceptEncoding != null && acceptEncoding.trim().equalsIgnoreCase(GZipUtil.ENCODING_GZIP)) { res.setHeader(GZipUtil.CONTENT_ENCOING, GZipUtil.ENCODING_GZIP); GZipResponse gzipResponse = new GZipResponse(res); //contentType为application/x-gzip, 代表客户端压缩了请求参数, 需要解压 String contentType = req.getContentType(); if (contentType != null && contentType.equalsIgnoreCase(GZipUtil.MIME_APPLICATION_X_GZIP)) { byte[] data = GZipUtil.unGzip(req.getInputStream()); Map<String, Object> newParameterMap = GZipUtil.getParameterMap(data, req); RequestParameterWrapper requestWrapper = new RequestParameterWrapper( req, newParameterMap); chain.doFilter(requestWrapper, gzipResponse); } else { chain.doFilter(req, gzipResponse); } } else { chain.doFilter(request, res); }
因为请求内容有被压缩的post参数未正常保存在request中, 建立新类RequestParameterWrapper(继承HttpServletRequestWrapper, 调用gzipUtil的方法处理)重新包装请求参数, 同时使用GZipResponse extends HttpServletResponseWrapper对响应数据进行gzip压缩.
RequestParameterWrapper:
public class RequestParameterWrapper extends HttpServletRequestWrapper { private final Map params; public RequestParameterWrapper(HttpServletRequest request, Map newParams) { super(request); this.params = newParams; } @Override public Map getParameterMap(){ return params; } @Override public Enumeration getParameterNames(){ Vector l = new Vector(params.keySet()); return l.elements(); } @Override public String[] getParameterValues(String name){ Object v = params.get(name); if(v == null){ return null; }else if(v instanceof String[]){ return (String[])v; }else if(v instanceof String){ return new String[]{(String)v}; }else{ return new String[]{v.toString()}; } } @Override public String getParameter(String name){ Object v = params.get(name); if(v == null){ return null; }else if(v instanceof String[]){ return ((String[])v)[0]; }else if(v instanceof String){ return (String)v; }else{ return v.toString(); } } }
GZipResponse:
-
public class GZipResponse extends HttpServletResponseWrapper { private GZIPServletStream wrappedOut; public GZipResponse(HttpServletResponse response) throws IOException { super(response); wrappedOut = new GZIPServletStream(response); } public ServletOutputStream getOutputStream() throws IOException { return wrappedOut; } private PrintWriter wrappedWriter; public PrintWriter getWriter() throws IOException { if (wrappedWriter == null) { wrappedWriter = new PrintWriter(new OutputStreamWriter( getOutputStream(), getCharacterEncoding())); } return wrappedWriter; } public void flush() throws IOException { if (wrappedWriter != null) { wrappedWriter.flush(); } } private class GZIPServletStream extends ServletOutputStream { private HttpServletResponse response; private OutputStream outputStream; private ByteArrayOutputStream baos = new ByteArrayOutputStream(); public GZIPServletStream(HttpServletResponse response) throws IOException { this.response = response; outputStream = response.getOutputStream(); } public void write(byte[] buf) throws IOException { baos.write(buf); } public void write(byte[] buf, int off, int len) throws IOException { baos.write(buf, off, len); } public void write(int c) throws IOException { baos.write(c); } public void write(byte buf) throws IOException { baos.write(buf); } public void flush() throws IOException { // byte[] bytes = baos.toByteArray(); // GZipUtil.gzip(bytes, outputStream); //需要把压缩后的字节大小设置到response的content-length中 byte[] bytes = GZipUtil.gzip(baos.toByteArray()); outputStream.write(bytes); outputStream.flush(); response.setHeader(GZipUtil.CONTENT_LENGTH, String.valueOf(bytes.length)); } public void close() throws IOException { outputStream.close(); } } }