servlet以及spring mvc实现bigpipe技术分享

使用Servlet分段输出构建BigPipe服务端
BigPipe是一个重新设计的基础动态网页服务体系。大体思路是,分解网页成叫做Pagelets的小块,然后通过Web服务器和浏览器建立管道并管理他们在不同阶段的运行。这是类似于大多数现代微处理器的流水线执行过程:多重指令管线通过不同的处理器执行单元,以达到性能的最佳。

使用JAVA实现BigPipe服务端的例子在网上很难寻觅,笔者经过多次尝试,在Servlet3.0和Servlet2.5规范下成功实现了BigPipe的分段输出效果。好东西不敢独享,在这里与大家分享。
要搭建BigPipe服务端程序,首先我们必须了解BigPipe服务端的工作原理。BigPipe的最终目标是通过一次请求向浏览器输出页面框架和若干个Pagelet。浏览器在接收完页面框架后立即进行展现,然后并行的接收后面的Pagelets,从而加快页面的渲染速度。Pagelet是通过调用前端的一个JS方法向页面输出的。
为了达到一次请求多次输出的效果,服务端程序必须采用分段输出的技术(chunked)。这里所说的chunked是指HTTP 1.1规范中定义的一个特殊的HTTP头信息,下面的一段HTTP响应头包含了chunked定义:

HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked

通过在HTTP响应头中增加“Transfer-Encoding: chunked”标识,我们通知浏览器后面的响应内容是分段输出的,每段输出使用一个16进制的数字来声明段长度,然后紧跟一个回行换行符,后面是分段的内容体,最后使用一个0长度的分段来结束整个的chunked输出。下面是一个用Socket模拟的chunked分段输出:

public static void main(String[] args) throws IOException, InterruptedException {  
    StringBuffer content1 = new StringBuffer("<html><body>");  
    content1.append("<div id='a1' style='width:100px;height:100px;border:1px solid black'></div>");  
    content1.append("<div id='a2' style='width:100px;height:100px;border:1px solid black'></div>");  
    content1.append("<div id='a3' style='width:100px;height:100px;border:1px solid black'></div>");  
    content1.append("<div id='a4' style='width:100px;height:100px;border:1px solid black'></div>");  
    content1.append("</body></html>");  
    String content2 = "<script type='text/javascript'>document.getElementById('a1').innerHTML='<h1>1</h1>'</script>";  
    String content3 = "<script type='text/javascript'>document.getElementById('a2').innerHTML='<h1>2</h1>'</script>";  
    String content4 = "<script type='text/javascript'>document.getElementById('a3').innerHTML='<h1>3</h1>'</script>";  
    String content5 = "<script type='text/javascript'>document.getElementById('a4').innerHTML='<h1>4</h1>'</script>";  
  
    ServerSocket ss = new ServerSocket(80);  
    Socket s;  
    while (true) {  
        s = ss.accept();  
        s.getInputStream().read();  
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(s.getOutputStream(), "UTF-8"));  
        //输出响应头  
        pw.println("HTTP/1.1 200 OK");  
        pw.println("Content-Type: text/html;charset=UTF-8");  
        //声明分段输出  
        pw.println("Transfer-Encoding: chunked");  
        pw.println();  
        //第一段  
        //先输出段长度,注意必须是16进制,加2是因为后面有个回车换行符  
        pw.println(Integer.toHexString(content1.length() + 2));  
        //输出分段内容  
        pw.println(content1.toString());  
        //输出回车换行符结束本段输出  
        pw.println();  
        //立即清空输出缓冲区,通知浏览器立即处理本段内容  
        pw.flush();  
        //暂停1秒模拟后台处理过程  
        Thread.sleep(1000);  
        //第二段  
        pw.println(Integer.toHexString(content2.length() + 2));  
        pw.println(content2);  
        pw.println();  
        pw.flush();  
        Thread.sleep(1000);  
        //第三段  
        pw.println(Integer.toHexString(content3.length() + 2));  
        pw.println(content3);  
        pw.println();  
        pw.flush();  
        Thread.sleep(1000);  
        //第四段  
        pw.println(Integer.toHexString(content4.length() + 2));  
        pw.println(content4);  
        pw.println();  
        pw.flush();  
        Thread.sleep(1000);  
        //第五段  
        pw.println(Integer.toHexString(content5.length() + 2));  
        pw.println(content5);  
        pw.println();  
        pw.flush();  
        //输出一个0长段结束整个输出  
        pw.println(0);  
        pw.println();  
        pw.close();  
        //最后别忘了关闭输出流  
        s.close();  
    }  
}  
运行上面的代码,在浏览器中输入"http://localhost"应该就可以看到分段输出的效果了,注意和传统的AJAX异步请求不同,这里只有一次请求!

通过上面使用Socket模拟的分段输出响应,我想大家已经了解了Bigpipe服务端工作的原理,下面我们通过Servlet来实现分段输出的效果。查找了好久,网上只能找到使用Servlet3.0实现分段输出的样例,得益于Servlet3.0规范新增的异步处理接口,我们可以使用很幽雅的方式来完成分段输出的效果,但我相信使用旧的Servlet规范一定也能实现分段输出的效果,事实上的确如此,我们先来看使用Servlet3.0实现的代码:

@WebServlet(value = { "/syncServlet" }, asyncSupported = true, loadOnStartup = 1)  
public class SyncTestServlet extends HttpServlet {  
    private static final long serialVersionUID = -126107068129496624L;  
    private final BlockingQueue<Runnable> quere = new ArrayBlockingQueue<Runnable>(10);  
    private final ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 10, TimeUnit.HOURS, quere);  
  
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
        response.setContentType("text/html");  
        final AsyncContext sc = request.startAsync(request, response);  
        final StringBuffer content = new StringBuffer("<html><body>");  
        content.append("<div id='a1' style='width:100px;height:100px;border:1px solid black'></div>");  
        content.append("<div id='a2' style='width:100px;height:100px;border:1px solid black'></div>");  
        content.append("<div id='a3' style='width:100px;height:100px;border:1px solid black'></div>");  
        content.append("<div id='a4' style='width:100px;height:100px;border:1px solid black'></div>");  
        content.append("</body></html>");  
        final PrintWriter pw = response.getWriter();  
        pw.println(content.toString());  
        pw.flush();  
        executor.execute(new Job(sc, 1));  
        executor.execute(new Job(sc, 2));  
        executor.execute(new Job(sc, 3));  
    }  
  
}  
  
class Job implements Runnable {  
    private static int count;  
    private AsyncContext syncContext;  
    private int no;  
  
    public Job(AsyncContext syncContext, int no) {  
        this.syncContext = syncContext;  
        this.no = no;  
        count++;  
    }  
  
    public void run() {  
        HttpServletResponse resp = (HttpServletResponse) syncContext.getResponse();  
        try {  
            Thread.sleep(no * 1000);  
            PrintWriter out = resp.getWriter();  
            out.println("<script type='text/javascript'>document.all.a" + no + ".innerHTML='<h1>" + no + "</h1>'</script>");  
            out.flush();  
        } catch (IOException e) {  
            e.printStackTrace();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        count--;  
        if (count == 0) {  
            syncContext.complete();  
        }  
    }  
}  

下面是使用旧的Servlet规范实现分段输出的代码,由于旧的Servet规范没有包含异步处理功能,我们只能自己处理线程的同步和页面输出流的抢占问题。

public class BigpipeServlet extends HttpServlet {  
    private static final long serialVersionUID = 1L;  
  
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
        response.setContentType("text/html;charset=UTF-8");  
        final PrintWriter pw = response.getWriter();  
        final StringBuffer content = new StringBuffer("<html><body>");  
        content.append("<div id='a1' style='width:100px;height:100px;border:1px solid black'></div>");  
        content.append("<div id='a2' style='width:100px;height:100px;border:1px solid black'></div>");  
        content.append("<div id='a3' style='width:100px;height:100px;border:1px solid black'></div>");  
        content.append("<div id='a4' style='width:100px;height:100px;border:1px solid black'></div>");  
        content.append("</body></html>");  
        pw.println(content.toString());  
        pw.flush();  
        final Paglet paglet1 = new Paglet(pw, 1);  
        final Paglet paglet2 = new Paglet(pw, 2);  
        final Paglet paglet3 = new Paglet(pw, 3);  
        paglet1.start();  
        paglet2.start();  
        paglet3.start();  
        try {  
            paglet3.join();  
            paglet2.join();  
            paglet1.join();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        pw.close();  
    }  
  
    class Paglet extends Thread {  
  
        private int id;  
        private PrintWriter out;  
  
        public Paglet(PrintWriter out, int id) {  
            this.out = out;  
            this.id = id;  
        }  
  
        @Override  
        public void run() {  
            try {  
                sleep(id * 1000);  
                final String content = "<script>document.all.a" + id + ".innerHTML='<h1>" + id + "</h1>'</script>";  
                synchronized (out) {  
                    out.println(content);  
                    out.println();  
                    out.flush();  
                }  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
  
}  
使用spring mvc来实现和bigpipe技术,可以采用servlet的原理,在所使用的controller方法获取到response对象,返回值设置成void,采用flush进行分块输出。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值