介绍bigpipe以及在django上的实现,bigpipedjango实现
关于BigPipe是在看一篇淘宝ued的官方博客上看到的,原文是说用nodejs做前后端分离的,只是稍微提了一下bigpipe。
感兴趣的同学也可以看一下那篇文章,http://ued.taobao.org/blog/2014/04/full-stack-development-with-nodejs/
于是百度之,发现bigpipe是由facebook最先提出,个人感觉是个非常有意思的想法。
关于bigpipe的介绍,网上有很多,这里简单说一下:
我们平常打开网页通常都是串行的,服务器收到请求后,开始各种渲染页面,等页面全部渲染好之后,再返回给浏览器,而在渲染过程中,浏览器则一直处于等待状态。
加入服务器有几个耗时的操作,总共需要花费10秒,则在这10秒钟内,浏览器属于一片空白,用户体验很不好。
而bigpipe则是服务器接受到请求之后,立马返回一段骨架html,但是不包括闭合的body和html标签,这时候response并没有结束,每当服务器端准备好一块数据,就立即flush给浏览器,浏览器在收到骨架html之后,就立即开始渲染,之后每得到一段数据都进行渲染。
这样的好处是,也许整个页面也需要10秒才能完全显示出来,但是浏览器在第一秒就开始有东西显示。
还有一点需要特别说明的是,bigpipe使用javascript渲染页面,也就是说返回的是一对script标签,里面是一段javascript代码,这样的好处是,渲染页面的时候不会被块位置束缚,并且服务器支持多线程处理的话,可以同时处理多块内容,哪块先处理好,就flush回浏览器,不用在意html代码的物理顺序。后面每段返回的pagelet
也许有人会有疑问,咋看起来,BigPipe和Ajax非常像,那他们有什么不同呢。
主要的不同点在于,Ajax每一块需要单独发送一个HTTP请求,建立连接的开销是比较大的,而BigPipe只有一个HTTP请求。所以Ajax相对于BigPipe来说,对服务器造成的压力更大。
网上关于BigPipe的实现有很多,php和node.js是用的比较多的。
下面说下在python django框架下,实现一个BigPipe的例子,也方便大家理解BigPipe的思想。
首先,我们创建一个骨架模板,这个就是服务器在接收请求后,立即返回的html:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta http-equiv="X-UA-Compatible" content="IE=edge"/> <meta name="viewport" content="width=device-width, initial-scale=1"> <script type="text/javascript" src="/site_media/js/prototype.js"></script> <script type="text/javascript" src="/site_media/js/prototypepatch.js"></script> <script type="text/javascript" src="/site_media/js/bigpipe.js"></script> <title>{{title}}</title> </head> <body > <div id="content_0"></div> <div id="content_1"></div> <div id="content_2"></div> <div id="content_3"></div> <div id="content_4"></div> <div id="content_5"></div> <div id="content_6"></div> <div id="content_7"></div> <div id="content_8"></div> <div id="content_9"></div>
这段代码主要是有10个div,待会儿我们返回的内容就放在这些div里面,注意这里没有 </body>和</html> 这里引入了三个js文件,我们后面再说。
然后就是接收请求的django view:
import time from django.http import StreamingHttpResponse from django.template.loader import render_to_string def test(request): return StreamingHttpResponse(stream_response_generator())
由于我们不立即返回整个请求,所以并不直接返回HttpResponse对象,而是返回StreamingHttpResponse对象,并且这里的stream_response_generator函数返回的是生成器。关于python里面的生成器,请百度关键字yield。
stream_response_generator方法如下:
def stream_response_generator(): yield render_to_string('bigpipe.html', {"title":"BigPipe Test Page"}) for x in range(0,10): is_last = False if x == 9: is_last = True pagelet = dict(id="content_%s" % x, get_html_content=x, get_css_resources="", get_js_resources="", is_last=is_last ) yield render_to_string('pagelet.html',{'pagelet':pagelet}) time.sleep(1) yield "</body></html>\n"
yield render_to_string('bigpipe.html', {"title":"BigPipe Test Page"}) 这句就是把骨架html返回。
之后每次都会返回一段pagelet,代码为:yield render_to_string('pagelet.html',{'pagelet':pagelet})。
之后休息1秒再返回下一段pagelet,这个是为了模拟服务器耗时操作。
最后返回</body>和</html>标签: yield "</body></html>\n"
这里用了一个叫pagelet.html的模板文件,如下:
<script type="text/javascript"> BigPipe.onArrive({ id: '{{ pagelet.id }}', innerHTML: '{% filter escapejs %}{{ pagelet.get_html_content }}{% endfilter %}', css_files: ["/site_media/css/head.css", "/site_media/css/home.css"], js_files: ["/site_media/js/utils.js"], is_last: {{ pagelet.is_last|yesno:"true,false" }} }); </script>
这段javascript里面用到的BigPipe对象就是我们一开始在骨架HTML中引入的bigpipe.js中提供的,bigpipe.js依赖于prototype库。
bigpipe.js的github地址为:https://github.com/msroot/bigpipe
这里简单说下几个参数的意思:
id: 这个就是要把html放到哪个id下面,比如我们要放到id为content_0的div下面,这里就填content_0;
innerHTML: 就是具体的html代码;
css_files: 这段html代码依赖的css,bigpipe.js会先加载css文件,并且相同的css文件只会加载一次;
js_files: 这段html代码依赖的js文件,bigpipe.js会最后加载js文件,基本是等所有pagelet加载完才开始加载js文件;
is_last: 标识这个pagelet是否是最后一个。
下图是用firebug看到的整个请求过程:
网页上的0到9,基本是一个一个出来的,间隔约1秒,整个请求是10.04秒,其中等待响应时间只有26毫秒。
还要注意一点就是响应头中的 Transfer-Encoding:chunked。也就是告诉浏览器,这个是分段返回的。
还有一点,apache等http服务器会对返回进行一定的缓存,也就是等有一定数量的文本再返回,这样我们如果直接跑上面的代码,不会得到我们预期的结果,这里需要把apache的mod_deflate模块给disable掉,在配置文件中加上:
SetEnvIf Request_URI ^/mysite no-gzip=1
关于更多disable mod_deflate模块的信息,可以参考http://stackoverflow.com/questions/1922934/how-to-disable-mod-deflate-in-apache2
至此,大概就可以了解了bigpipe的整个思想,以及在django上的实现。
最后总结一下:
BigPipe是个非常有意思的想法,并且已经在Facebook以及淘宝等大型公司使用了比较长时间。很可能是未来前端优化,提升用户体验的主要手段。
还有一点不足,由于要依赖于客户端javascript进行一部分html渲染的工作,所以服务器端返回的pagelet中的js代码要依赖于浏览器中javascript的实现,有些库用innerHTML表示需要填充的html,有些库用content等(一开始就吃了这个亏,还好可以看bigpipe.js的源代码)。所以貌似没有一个统一的标准,这样不方便写通用的库。
一些介绍BigPipe的文章:
http://www.searchtb.com/2011/04/an-introduction-to-bigpipe.html
http://huoding.com/2011/06/26/88
javascript的bigpipe:
https://github.com/msroot/bigpipe
一个django关于bigpipe的扩展,但是4年前就没更新了,可以看看源码,领会精神:
https://github.com/orygens/django-bigpipe
https://pypi.python.org/pypi/django-bigpipe
django关于bigpip的一些内容:
http://www.slideshare.net/gagedark/even-faster-django-27352247 (需要翻墙)
http://www.slideshare.net/slawdan/bigpipe1126adev (也需要翻墙)
django返回stream content:
http://stackoverflow.com/questions/2922874/how-to-stream-an-httpresponse-with-django
Node.js的bigpipe实现:
https://bigpipe.io/
https://github.com/bigpipe/bigpipe.js