前言
最近的做的项目中,遇到这么一个问题:服务器返回给客户端的xml报文数据量太大,费时耗流量,于是要求服务端添加gzip支持,现在把整个流程写下来,以供以后有需要时参考。gzip是一种文件压缩算法,http服务器端添加支持后,客户端请求时添加请求头信息Accept-Encoding:gzip即可享受服务端的gzip压缩服务,获取压缩后的数据,极大减小网络传输数据,提高响应速度和减少流量。当然,如果客户端http请求中没有添加Accept-Encoding:gzip请求头信息,服务器端返回的依旧是未压缩的数据。
服务端gzip支持
首先,写一个简单的servlet程序,读取一个文本文件,然后返回给客户端
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException {
String xmlFileName = "books.xml";
try {
response.setContentType("text/html;charset=utf-8");
String filePath=this.getServletConfig().getServletContext().getRealPath("/"+xmlFileName);
File file = new File(filePath);
//文件原本大小
System.out.println("服务端文件大小:"+file.length()+"byte");
FileInputStream fileIn =new FileInputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int i = 0;
while ((i = fileIn.read(b)) != -1) {
baos.write(b, 0, i);
}
ServletOutputStream out = response.getOutputStream();
out.write(baos.toByteArray());
out.flush();
out.close();
fileIn.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
然后,tomcat配置gzip支持,具体做法是在server.xml配置文件中添加以下属性
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"
compression="on"
compressionMinSize="2048"
noCompressionUserAgents="gozilla"
compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/javascript"/>
主要参数解释
compression:开启压缩,可选值:"on"开启,"off"关闭,"force"任何情况都开启
compressionMinSize:指定压缩的最小数据大小,单位B,默认2048B
noCompressionUserAgents:正则表达式,匹配的UA将不会被压缩,默认空
compressableMimeType:会被压缩的MIME类型列表,多个逗号隔开,默认值:text/html,text/xml,text/plain,text/css,text/javascript,application/javascript
具体参考官网: Apache Tomcat 7 Configuration Reference (7.0.109) - The HTTP Connector
具体参考官网: Apache Tomcat 7 Configuration Reference (7.0.109) - The HTTP Connector
客户端请求及解压缩
首先,通过HttpURLConnection发起请求,看一看未添加Accept-Encoding:gzip请求头的情况,代码如下 public static void main(String[] args) throws Exception{
StringBuffer url = new StringBuffer("http://127.0.0.1:8080/GZip/GetBook");
URL u = new URL(url.toString());
HttpURLConnection conn;
InputStream in;
conn = (HttpURLConnection) u.openConnection();
conn.setRequestMethod("POST");
conn.setDoInput(true);
conn.setDoOutput(true);
in = conn.getInputStream();
System.out.println("客户端获取数据大小:"+in.available()+"byte");
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String line = null;
StringBuffer sb = new StringBuffer();
while((line = reader.readLine())!= null){
sb.append(line);
}
//打印获取到的数据
System.out.println(sb.toString());
in.close();
conn.disconnect();
}
控制台信息:
![](https://i-blog.csdnimg.cn/blog_migrate/a4384a5746d5326875b5822f6a3c7ad2.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3226ba1e0a053a254624cbb96510b698.png)
数据未压缩,数据大小一致
接着,再来看看添加Accept-Encoding:gzip请求头的情况,在上面的客户端代码添加下面一句
conn.addRequestProperty("Accept-Encoding", "gzip");
请求下:
![](https://i-blog.csdnimg.cn/blog_migrate/da4d0a68f359116fa9d7f4b943da2ef1.png)
数据已经被压缩,有原来的8365bB变成258B,gzip格式的数据显示为乱码,所有现在需要做的是对数据进行解压缩,使用JDK自带的GZIPInputStream就能搞定
GZIPInputStream gzipIn = new GZIPInputStream(in);
BufferedReader reader = new BufferedReader(new InputStreamReader(gzipIn));
结果如下
![](https://i-blog.csdnimg.cn/blog_migrate/7b168ea5bb12fdb476eccdf76faac0d1.png)
到此,整个流程基本结束
再插一脚
删除books.xml中的部分数据,使其大小小于配置的最小压缩数据2048B,再次请求,会发现数据不会被压缩
![](https://i-blog.csdnimg.cn/blog_migrate/e75c7ac155174607e9f2f1427435b2d9.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7e3c88f86a7bf303fa357023f137ee67.png)
因为数据格式不是gzip压缩格式,所有使用GZIPInputStream解压缩抛异常了
还有一点,浏览器默认请求头包含Accept-Encoding:gzip,而且会自动解压缩,浏览器不会显示乱码,另外,对于请求头中的deflate和gzip类似,也是一种压缩算法,不过多介绍。
![](https://i-blog.csdnimg.cn/blog_migrate/8c408c77d180c81f69f83bc8cd86c828.png)
插曲
之前通过另一种方式写的服务端发生了件有意思的事,代码如下
PrintWriter out = response.getWriter();
BufferedReader br = new BufferedReader(new InputStreamReader(fileIn));
StringBuffer sb = new StringBuffer();
String line = null;
while ((line = br.readLine())!=null){
sb.append(line);
}
out.write(sb.toString());
然后发现客户端的数据大小总比服务端的少了点,找了好久才查出原因。原来是BufferedReader的readLine方法会把每行的回车换行去掉,UTF-8编码下CRLF各占一个字符,所有,数据大小自然变小了。真是浅草才能没马蹄啊,囧。