伸手党请绕路!

首先普及知识:

gzip:一种web常见的压缩格式,压缩率大致50%,能极大降低带宽占用量,gzip 格式: 10字节首部+数据部分+8字节尾部。

chrunked:http1.1功能,支持内容分段传送给browser,对于后端逻辑比较耗时的页面,可以先给用户展示一部分内容,减少白屏时间。

使用nginx作为反向代理的话请确保nginx使用的是http1.1 长连接。

一下是tomcat与resin的实现方式:

tomcat:

tomcat比较简单,默认是支持trunked模式的,gzip开启方式如下:

找到tomcat安装目录的conf/server.xml,加上如下信息:

对应的jsp位置加上out.flush()将数据刷给browser:

就能见效。

对于resin,或者是需要对内容进行过滤再输出的情况,就需要先将数据flush到自定义的缓冲区中,以便于我们对内容进行过滤。

这里我们需要使用自定义的response(继承自HttpServletResponseWrapper)替换原来的HttpServletResponse,见代码:

public class ResponseWrapper extends HttpServletResponseWrapper implements AutoCloseable {//数据存储区private AsyByteArrayOutputStream bufferOutStream;private ServletOutputStream servletOutputStream;private PrintWriter printWriter;private HttpServletResponse response;private GzipWrapper gzipWrapper;public ResponseWrapper(HttpServletResponse response) throws IOException {super(response);this.bufferOutStream = new AsyByteArrayOutputStream(1000);this.printWriter = new PrintWriter(new OutputStreamWriter(bufferOutStream, "utf-8"), false);this.response = response;this.servletOutputStream = new WrapperServletOutStream(this.bufferOutStream);this.gzipWrapper = new GzipWrapper(1024);}@Overridepublic PrintWriter getWriter() {return this.printWriter;}@Overridepublic ServletOutputStream getOutputStream() throws IOException {return super.getOutputStream();}@Overridepublic void reset() {this.bufferOutStream.reset();}public void flushBuffer() throws IOException{this.printWriter.flush();this.servletOutputStream.flush();}public byte[] getResponseData() throws IOException{this.flushBuffer();byte[] dataArr = this.bufferOutStream.toByteArray();this.reset();return dataArr;}/*** @description: send datas to browser* @date 16:26 2020/3/1* @param jspWriter* @param lastTrunked* @return void*/public void writerToClient(JspWriter jspWriter, boolean lastTrunked) throws IOException{if (jspWriter != null){jspWriter.flush();}byte[] rowData = this.getResponseData();
//        System.out.println("rowData:" + new String(rowData));byte[] gzipData = this.gzipWrapper.writer(rowData, lastTrunked);//        System.out.println("row data size:" + rowData.length);
//        System.out.println("gzip Data size:" + gzipData.length);ServletOutputStream rsOutStream = this.response.getOutputStream();rsOutStream.write(gzipData);rsOutStream.flush();}@Overridepublic void close() throws Exception {this.gzipWrapper.close();}/*** @description:将自定义的buffer关联servletOutputStream,以便于通过stream能写入新的buffer* @exception * @return */private class WrapperServletOutStream extends ServletOutputStream{private AsyByteArrayOutputStream outputStream;public WrapperServletOutStream(AsyByteArrayOutputStream outputStream) {this.outputStream = outputStream;}@Overridepublic boolean isReady() {return true; }@Overridepublic void setWriteListener(WriteListener writeListener) {}@Overridepublic void write(int b) throws IOException {outputStream.write(b);}}/*** @description: copy from java.io.ByteArrayOutputStream,  remove synchronize* @see ByteArrayOutputStream*/public class AsyByteArrayOutputStream extends OutputStream {protected byte buf[];protected int count;public AsyByteArrayOutputStream() {this(32);}public AsyByteArrayOutputStream(int size) {if (size < 0) {throw new IllegalArgumentException("Negative initial size: "+ size);}buf = new byte[size];}private void ensureCapacity(int minCapacity) {// overflow-conscious codeif (minCapacity - buf.length > 0)grow(minCapacity);}private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = buf.length;int newCapacity = oldCapacity << 1;if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);buf = Arrays.copyOf(buf, newCapacity);}private int hugeCapacity(int minCapacity) {if (minCapacity < 0) // overflowthrow new OutOfMemoryError();return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;}public void write(int b) {ensureCapacity(count + 1);buf[count] = (byte) b;count += 1;}public void write(byte b[], int off, int len) {if ((off < 0) || (off > b.length) || (len < 0) ||((off + len) - b.length > 0)) {throw new IndexOutOfBoundsException();}ensureCapacity(count + len);System.arraycopy(b, off, buf, count, len);count += len;}public void writeTo(OutputStream out) throws IOException {out.write(buf, 0, count);}public void reset() {count = 0;}public byte toByteArray()[] {return Arrays.copyOf(buf, count);}public int size() {return count;}public String toString() {return new String(buf, 0, count);}public String toString(String charsetName)throws UnsupportedEncodingException{return new String(buf, 0, count, charsetName);}@Deprecatedpublic String toString(int hibyte) {return new String(buf, hibyte, 0, count);}public void close() throws IOException {}}
}

通过以上代码我们能实现将页面数据存储到自定义的bufferOutStream中。

接着在需要分段输出的位置调用封装好的方法进行向browser写数据的操作,注意,由于页面数据被存储到我们自定义的buffer中,需要在filter中对最后一段数据进行flush操作,否则浏览器得到的页面数据就是不完整的了。

上述代码还使用了自定义的gzip实现,因为浏览器不支持一个页面分成多个gzip压缩包的形式,要是压缩成多个gzip发送给browser,browser只会显示第一个包的数据。这里参考了tomcat的实现思路,实际上tomcat的实现复杂得多,功能也更加强大(关于tomcat的实现有兴趣可以去查看tomcat的源码),以及借助jdk1.7在GZIPOutputStream新增的方法,对页面的数据进行分段压缩进同一个gzip包,每压缩进一段数据就通过该段数据在gzip格式buffer中的起止位置将数据拷贝过来(要是最后一块数据,则调用GZIPOutputStream的finish方法以完成压缩,将gzip的尾部也发送给browser。),再将该段数据再发送给broswer,这样在不损害gzip包格式的情况下,浏览器接受的就是同一个gzip包,就能正常显示出页面了。具体代码如下:

public class GzipWrapper implements AutoCloseable{//buffer,存储gzip数据  gzip 格式:   10字节首部,8字节尾部private ByteArrayOutputStream byteArrOutStream;private GZIPOutputStream gzipOutputStream;//下一次读取的起始位置private int nextReadStart = 0;public GzipWrapper(int size) throws IOException {this( new ByteArrayOutputStream(size) );}public GzipWrapper(ByteArrayOutputStream byteArrOutStream) throws IOException {this.byteArrOutStream = byteArrOutStream;//同步刷新很重要,jdk  min version 1.7this.gzipOutputStream = new GZIPOutputStream(this.byteArrOutStream, true);}/*** @Description:压缩成gzip,再分段返回压缩的数据* @date 13:50 2020/3/2 0002* @param data* @param lastTrunked* @return byte[]*/public byte[] writer(byte data[], boolean lastTrunked) throws IOException{this.gzipOutputStream.write(data, 0, data.length);this.gzipOutputStream.flush();//是最后一段数据则完成压缩,返回剩下的数据,包括gzip 压缩包的尾部格式数据if (lastTrunked){this.gzipOutputStream.finish();}byte[] allGzipData = this.byteArrOutStream.toByteArray();int dataLen = allGzipData.length - this.nextReadStart;byte[] gzipArr = new byte[dataLen];System.arraycopy(allGzipData, this.nextReadStart, gzipArr, 0, dataLen);this.nextReadStart += dataLen;return gzipArr;}@Overridepublic void close() throws Exception {this.gzipOutputStream.close();}
}

值得注意的是,要是jsp页面有重定向或者cookie操作的话,需要将对应的操作逻辑写到第一个trunked输出的前面,不然浏览器收到第一个包的时候,header等信息已经收到了,后续对其修改将无效。

git  demo地址:https://gitee.com/tandatda/base.git

gzip+chunked页面分段输出,resin gzip trunked无效,页面数据写入自定义buffer相关推荐

  1. Html页面上输出不了PHP,在页面上直接输出未经解析的HTML源码

    摘要:<?php $str6=' $我在\'php中文网\'学习 PHP 和 HTML ,目前我还是&小白&. '; echo $str6,' ';//\', . $str6=' ...

  2. gzip chunked

    gzip chunked --=比天空间=-- - mod_gzip和chunked http://www.httpwatch.com/httpgallery/chunked/ When an HTT ...

  3. php设置curl gzip,curl配置CURLOPT_ENCODING选项解压缩gzip文件响应

    上周尝试用teleport采集一个小站(偶尔玩一下,绝无恶意),结果发现采集到的第一个文件就是乱码,当然无法继续了.后来又用wget和curl尝试了一下,发现得到的同样是乱码. 于是,开始初步分析了一 ...

  4. asp.net webform html,ASP.NET WebForm页面内容输出方式

    这次我们谈的话题是"Web Form页面上输出内容的方式".这其实是一个非常旧的话题了,因为本文的内容甚至可以运用于ASP.NET 1.1之上.不过这个话题的适用范围很广,因为即使 ...

  5. linux apache gzip filters,Linux Apache2如何开启gzip (deflate module) 压缩功能

    检查你的网站是否启用了gzip,用chrome打开网站比如http://www.techbrood.com 选中一个请求,查看Response header部分,如果没有包含 Content-Enco ...

  6. 在JSP页面中输出JSON格式数据

    JSON-taglib是一套使在JSP页面中输出JSON格式数据的标签库. JSON-taglib主页: http://json-taglib.sourceforge.net/index.html J ...

  7. java web统计报表_用Java在Web页面上输出统计图 统计报表

    用Java在Web页面上输出统计图 河南省焦作水利局 聂春生 在Internet 和Intranet 的应用中,数据库和Web 技术的结合是传统MIS 系统移植到Internet(Intrant) 环 ...

  8. php 微信emoji输出,页面如何输出Emoji表情字符

    Emoji表情符号广泛运用于各种的社交软件上,是非常受欢迎的一种字符,如果用语言文字无法表达你内心的冲动,发一个Emoji表情就好了. 对于web而言如何在页面保存和展现这些字符呢?这可不是件容易的事 ...

  9. php获取页面输出内容,PHP CURL获取页面内容输出例子

    使用PHP curl获取页面内容或提交数据,有时候希望返回的内容作为变量储存,而不是直接输出.这个时候就必需设置curl的CURLOPT_RETURNTRANSFER选项为1或true. 1.curl ...

最新文章

  1. 汇编语言 实现一个数字的平方
  2. element ui走马灯怎么添加_Lovestu - Element UI 走马灯高度自适应
  3. 为什么接吻需要闭眼睛?
  4. c语言如何输出一维数组字母,C语言一维数组初步学习笔记
  5. C语言在main中输入2个整数ab,请教一个C语言问题。若输入3个整数3,2,1,则以下程序输出的结果是.,c 语言问题 输入三个整数 按从小到大输出...
  6. 跨设备链路聚合_企业核心经常用到的链路聚合技术,原理与实现
  7. mysql left join 耗时_性能调优:mysql之left join
  8. WordPress直接调用头像地址
  9. 副屏幕全屏_如何让电脑显示器屏幕显示全屏
  10. 【KDD2021】Are we really making much progress? Revisiting, benchmarking, and refining HGNNs
  11. java简易计算器报告_JAVA实训报告简易计算器.doc
  12. 2021-03-22 什么是SFP端口
  13. 注册公司流程和费用(2016版)
  14. 联想电脑为什么没有计算机,联想笔记本电脑没有声音怎么办
  15. 2022年湖北中级工程师职称评审详细流程是哪些步骤呢? 甘建二
  16. 两个故事讲述什么是消息队列
  17. Kafka集群的安全认证机构 SASL_SCRAM
  18. #433 津津有味:红黑榜 | 要不是为了热量缺口,谁爱吃它!
  19. 推荐一款臭虫药,很有效,亲测。
  20. 简单编程(六)使用for循环语句 编写程序1+2+3+...........+100

热门文章

  1. 今天我解决的sql中文乱码问题
  2. Ant学习总结5(配合Ant视频8,9)
  3. 驱动依赖_「世经研究」焦炭行业--仍旧依赖政策驱动
  4. matlab用ezmesh绘制单位球,Matlab------------命令大全2
  5. html 接收 图片流_Microsoft Flow 利用自动化工作流增强Power BI使用效率
  6. 蓄水池问题c语言编程,蓄水池大作战
  7. mysql查看数据倾斜_深入理解hadoop数据倾斜
  8. java对xml文件的解析_Java对XML文件的解析
  9. windows更新不支持的硬件_Win10五月大更新细节,解决高CPU占用,不支持AMD
  10. C#总结项目《汽车租聘系统》项目代码实例【全注释版】