js能否在一个http长请求中多次进行数据交互?

  1. 描述你的问题
    能否在请求服务器端执行一个耗时较长的任务时,过程中会返回进度,利用http长连接

  2. 服务器端环境为 lighttpd + CGI(linux c实现,非fastcgi)

  3. 例如这里的代码,http发起一次请求后,需要等待服务器端CGI进程执行结束后才能得到返回值,而CGI在执行期间会不停打印进度。导致只有执行到100%的时候才一次性返回所有值"1%2%3%...100%",而不是依次返回更新进度。

    $.post(url, data, function(progress) {
        update(progress);
    });
阅读 6.4k
评论
    6 个回答

    前面几位大神说的都对,但是题主的要求Ajax是无法实现的。

    • Ajax都是一次请求结束了,调用一个回调来通知

    • 实时的获取数据的动态变化做不到

    感谢 @白一梓 让我知道了XMLHttpRequest level 2 API(对以上言辞做修改),于是有了第4种方法

    提供几种方法:

    1. 用EventSource(这个个个浏览器的支持不一,IE就不支持)看这两个就能看懂MDN EventSource这里面有个例子

    2. WebSocket

    3. 内嵌一个隐藏的iframe,iframe的src属性指向你的长耗时任务的URL,然后写一个Timer不停的去刷新iframe中的内容,根据内容字符串的最后一个百分号的值,获得进度。

    4. 使用ajax XMLHttpRequest level 2 API

    PS: 另外CGI的机制不太清楚,是CGI执行完毕,一次性的获得stdout的内容,一次性传回,还是一边执行CGI一边传输stdout的输出,如果是前置,那就没办法了

    这里把方法4做个例子:
    服务器端用nodejs写一个模拟(Node.js >= v4.x)

    'use strict';
    
    const http = require('http');
    
    const server = http.createServer();
    server.on('request', function(req, res) {
        console.log('HTTP', req.method, req.url);
        let n = 0;
        res.writeHead(200, {
            'Content-Type': 'text/plain',
            'Connection': 'keep-alive',
            'Access-Control-Allow-Origin': '*',
            'Content-Length': 100
        });
    
        const inter = setInterval(function() {
            res.write('.');
            n++;
            if (n >= 100) {
                clearInterval(inter);
                res.end();
            }
        }, 50);
    
        res.on('error', function(err) {
            console.log(err);
        });
    
        res.on('close', function() {
            console.log('Connection close');
        })
    });
    
    server.listen(8000);
    

    网页如下:

    <!DOCTYPE html>
    <HTML>
    <head>
        <meta charset="utf-8"/>
        <style>
            .progress-bar {
                border: 1px solid rgb(230, 230, 230);
                padding: 0px;
                position: relative;
                height: 4px;
                border-radius: 2px;
            }
    
            .progress-bar > .progress-bar-inner {
                margin: 0px;
                width: 30%;
                background-color: rgb(0, 180, 20);
                height: 4px;
                border-radius: 2px;
            }
        </style>
    </head>
    <body>
    <div class="progress-bar"><div id="pro" class="progress-bar-inner"></div></div>
        <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
        <script>
            var progress = $('#pro');
            var xhr = new XMLHttpRequest();
            xhr.open('GET', 'http://localhost:8000/', true);
            xhr.onprogress = function(event) {
                if (event.lengthComputable) {
                    var pro = event.loaded/event.total;
                    console.log(pro);
                    progress.css('width', pro*100 + '%');
                }
            };
            xhr.send();
        </script>
    </body>
    </HTML>
    
      • 2.4k

      使用websocket可以满足题主的需求

        长连接是对底层的TCP连接来说的。意思是一个TCP连接可以给多个HTTP连接复用,从而节省建立和断开连接的时间,提高效率。但是对HTTP来说并没有长连接短连接之分,浏览器接收完服务器发送的响应数据之后本次HTTP请求就完成了。

          获取ajax加载进度请看阮一峰的文章XMLHttpRequest Level 2 使用指南的第九节。你可以设置返回http头部content-length为100字节,在每完成一部分任务时,就返回一部分数据,所有任务完成后返回最后的第100字节。只给一个思路,具体我没有试验过。

            • 18.7k

            长轮询(long polling)
            你可以通过ajax请求来模拟HTTP长连接,这个需要服务器的配合,服务器端将数据返回给前端后,并不关闭连接~~
            同时ajax再获取到数据后再次发送http请求~~
            Comet就是这样的一个类似实现

              感谢楼上各位大神的回答,总结下最后的思路大概是这个样子的
              响应头中写明响应长度,这里为100,当返回的响应长度不够时,该请求不会结束

              res.writeHead(200, {
                  'Content-Type': 'text/plain',
                  'Connection': 'keep-alive',
                  'Access-Control-Allow-Origin': '*',
                  'Content-Length': 100
              });

              然后服务端每执行一部分后输出几个字节,这里是每执行1%的工作,写1字节,这样就会触发js端xhr的event事件,该事件的total是响应头中的content-length,这里是100,而lengthComputable是已经收到的字节数。所以通过服务器和前端的配合,这样就可以计算出执行的百分比。

              目前通过nodejs做服务器是成功的,cgi还需要测试写一个字节,能否立刻触发前端的event事件,不过问题基本算是解决了,再次谢谢各位!

                撰写回答

                登录后参与交流、获取后续更新提醒