我曾经在2011年写过一个关于ajax使用Streaming获取推送数据的文章:
https://www.queyang.com/blog/archives/54 让ajax更加完美——Ajax Streaming研究和使用

Ajax Streaming常用的场景:

  • 你通过AJAX向服务器提交一个请求,服务器会在接下来一段时间(比如1分钟)向客户端推送一些执行的信息,这样可以实时的看到进度,而传统的方式只能等到服务器执行完毕才能知道结果。
  • 通过ajax获取服务器上一些实时数据,却不想使用定时(比如间隔1秒)ajax不断的请求
  • 不需要客户端向服务器反馈交互的服务器推送数据显示方式

当然,在html5中有websocket可以满足页面和服务器交互的操作,这个是ajax stream所做不到的。

之前那篇文章简单介绍了使用stream的原理,然而jQuery一直不支持,今天闲来写了一个扩展jQuery的插件,可以让jQuery直接支持使用,增加了一个 message 的参数,当收到内容当前的内容会存放在 jqXHR.currentResponseText 中并回调message方法执行(注意:老版本的IE浏览器不支持,若需要支持,得用那篇在2011年写的那个文章的模拟方法)

此扩展支持HTTP的分块协议,可以自动将分块的长度数据给自动过滤只保留正文部分,HTTP分块协议见: http://zh.wikipedia.org/wiki/分块传输编码

插件扩展

(function ($) {
    var ajax = $.ajax;

    $.ajax = function (options)
    {
        var jqXHR;
        if ($.isFunction(options.message))
        {
            if ($.isFunction(options.xhr))
            {
                options._xhr = options.xhr;
            }
            options.xhr = function() {
                try
                {
                    var xmlhttp = $.isFunction(this._xhr)? this._xhr() : new XMLHttpRequest();

                    var self = this;
                    xmlhttp.streaming = {
                        oldResponseLength : 0
                    };

                    var hexdec = function (hex_string)
                    {
                        hex_string = (hex_string + '').replace(/[^a-f0-9]/gi, '');
                        return parseInt(hex_string, 16);
                    };

                    /**
                     * 将HTTP协议中定义分块大小的长度部分移除
                     *
                     * @see http://zh.wikipedia.org/wiki/%E5%88%86%E5%9D%97%E4%BC%A0%E8%BE%93%E7%BC%96%E7%A0%81
                     * @param data
                     * @returns string
                     */

                    var format = function(data)
                    {
                        if (data==="0\r\n\r\n")return '';   //分块输出结束符

                        data = $.trim(data);
                        var d = data.indexOf("\r\n");
                        if (d)
                        {
                            var len = hexdec(data.substr(0, d));

                            if (len>0 && len + 2 + d == data.length)
                            {
                                data = data.substr(d+2);
                            }
                        }

                        return data;
                    };

                    xmlhttp.onreadystatechange = function()
                    {
                        if (this.readyState == 3)
                        {
                            var data = this.responseText;
                            if (this.streaming.oldResponseLength>0)
                            {
                                data = data.substr(this.streaming.oldResponseLength);
                            }
                            this.streaming.oldResponseLength = this.responseText.length;

                            data = format(data);
                            if (data.length>0)
                            {
                                jqXHR.currentResponseText = data;
                                self.message.call(self, jqXHR, 'stream');
                            }
                        }
                    };
                    return xmlhttp;
                }catch(e){}
            };

            var obs = null;
            if ($.isFunction(options.beforeSend))
            {
                obs = options.beforeSend;
            }

            options.beforeSend = function(xhr) {
                jqXHR = xhr;
                if (obs)return obs.apply(this, arguments);
            };
        }

        return ajax(options);
    }
})(jQuery);

使用方法:

$.ajax({ url: "test.php", complete: function(){
    console.log('done');
}, message: function(xhr) {
    // 当收到消息时调用
    console.log(xhr.currentResponseText);
}});

test.php 代码例子

<?php
// 先输出1024长度内容,避免firefox等不识别
echo str_pad('', 1024), "\r\n";
flush();   // 将内容推送到浏览器

for($i=0; $i<10; $i++)
{
    echo 'step:'.$i.' time:'.time();
    flush();  //推送数据
    sleep(1);
}