一旦数据在服务器上可用,就在网页上显示结果

时间:2009-12-09 13:01:00

标签: python cgi

我正在用Python编写一个cgi页面。假设客户端向我的cgi页面发送请求。我的cgi页面进行计算,一旦它有第一个输出,它就会将该输出发送回客户端,但它将 CONTINUE 进行计算并发送其他响应 AFTER 发送第一个响应。

我在这里展示的是可能的吗?我问这个问题是因为在我有限的知识中,在cgi页面中,响应是一次性发回的,一旦发送响应,cgi-page就会停止运行。这件事是在服务器端或客户端进行的,我该如何实现呢?

我的服务器正在运行Apache。非常感谢你。

我在这个论坛中尝试过“dbr”的客户端代码(感谢他,我知道了多长时间轮询的工作原理)。

<html>
<head>
    <title>BargePoller</title>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js" type="text/javascript" charset="utf-8"></script>

    <style type="text/css" media="screen">
      body{ background:#000;color:#fff;font-size:.9em; }
      .msg{ background:#aaa;padding:.2em; border-bottom:1px #000 solid}
      .old{ background-color:#246499;}
      .new{ background-color:#3B9957;}
    .error{ background-color:#992E36;}
    </style>

    <script type="text/javascript" charset="utf-8">
    function addmsg(type, msg){
        /* Simple helper to add a div.
        type is the name of a CSS class (old/new/error).
        msg is the contents of the div */
        $("#messages").append(
            "<div class='msg "+ type +"'>"+ msg +"</div>"
        );
    }

    function waitForMsg(){
        /* This requests the url "msgsrv.php"
        When it complete (or errors)*/
        $.ajax({
            type: "GET",
            url: "msgsrv.php",

            async: true, /* If set to non-async, browser shows page as "Loading.."*/
            cache: false,
            timeout:50000, /* Timeout in ms */

            success: function(data){ /* called when request to barge.php completes */
                addmsg("new", data); /* Add response to a .msg div (with the "new" class)*/
                setTimeout(
                    'waitForMsg()', /* Request next message */
                    1000 /* ..after 1 seconds */
                );
            },
            error: function(XMLHttpRequest, textStatus, errorThrown){
                addmsg("error", textStatus + " (" + errorThrown + ")");
                setTimeout(
                    'waitForMsg()', /* Try again after.. */
                    "15000"); /* milliseconds (15seconds) */
            },
        });
    };

    $(document).ready(function(){
        waitForMsg(); /* Start the inital request */
    });
    </script>
</head>
<body>
    <div id="messages">
        <div class="msg old">
            BargePoll message requester!
        </div>
    </div>
</body>
</html>

这是我的服务器代码:

import sys
if __name__ == "__main__":
    sys.stdout.write("Content-Type: text/html\r\n\r\n")
    print "<html><body>"
    for i in range(10):
        print "<div>%s</div>" % i
        sys.stdout.flush()
    print "</body></html>"

我希望我的客户端页面一次显示1个数字(0,1,2,...),但数据总是一次出现(01234 ...)。请帮我搞清楚。非常感谢你们。

稍微偏离轨道,我正在尝试使用jquery comet插件,但我找不到足够的文档。非常感谢帮助。再次感谢:D

[编辑]好的家伙,最后感谢您的指南,我已设法使其成功。当你预测mod_deflate是所有这些的来源时,你是对的。

总结一下,我在这里做了什么:

  • 对于客户端,请将长轮询页面设为上面的html代码

  • 对于服务器,通过以下方式禁用mod_deflate:编辑文件/etc/apache2/mods-available/deflate.conf,注释掉带有text / html部分的行并重新启动服务器。要确保Python不缓冲输出本身,请在页面开头包含#!/ usr / bin / python -u。请记住在每次打印后要在客户端显示时使用sys.stdout.flush()。效果可能不透明,应该包括time.sleep(1)来测试。 :d

非常感谢你们支持和帮助解决这个问题:D

4 个答案:

答案 0 :(得分:8)

不确定

传统的服务器驱动方法,脚本只运行一次,但需要很长时间才能完成,随着时间的推移吐出一些页面:

import sys, time

sys.stdout.write('Content-Type: text/html;charset=utf-8\r\n\r\n')

print '<html><body>'
for i in range(10):
    print '<div>%i</div>'%i
    sys.stdout.flush()
    time.sleep(1)

当向WSGI编写应用程序时,这是通过让应用程序返回一个iterable来完成的,该iterable输出它想要一次一个地发送的每个块。我真的建议写信给WSGI;您现在可以通过CGI进行部署,但是将来当您的应用程序需要更高的性能时,您可以通过更快的服务器/接口进行部署,而无需重写。

WSGI-over-CGI示例:

import time, wsgiref.handlers

class MyApplication(object):
    def __call__(self, environ, start_response):
        start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
        return self.page()

    def page(self):
        yield '<html><body>'
        for i in range(10):
            yield '<div>%i</div>'%i
            time.sleep(1)

application= MyApplication()
if __name__=='__main__':
    wsgiref.handlers.CGIHandler().run(application)

请注意,您的Web服务器可能会通过添加自己的缓冲来阻止此方法(对于CGI WSGI)。如果您使用mod_deflate之类的输出转换过滤器来自动压缩webapp输出,则通常会发生这种情况。您需要为部分响应生成脚本关闭压缩。

这限制了您在新数据进入时逐位呈现页面。您可以通过让客户端在新数据进入时更改页面来使其变得更漂亮,例如:

def page(self):
    yield (
        '<html><body><div id="counter">-</div>'
        '<script type="text/javascript">'
        '    function update(n) {'
        '        document.getElementById("counter").firstChild.data= n;'
        '    }'
        '</script>'
    )
    for i in range(10):
        yield '<script type="text/javascript">update(%i);</script>'%i
        time.sleep(1)

这依赖于客户端脚本,因此最后在最后包含基于非脚本的备份最终输出可能是个好主意。

一直这样做,页面似乎仍在加载。如果您不希望这样,那么您需要将脚本拆分为第一个只扫出静态内容的请求,包括客户端脚本,该脚本使用一个XMLHttpRequest检查服务器,以便轮询新数据通过,或者,对于真正长期运行的情况,许多XMLHttpRequests返回状态和任何新数据。这种方法要复杂得多,因为这意味着您必须将工作流程作为后台守护进程运行,而不是Web服务器,并使用例如在守护进程和前端CGI / WSGI请求之间传递数据。管道或数据库。

答案 1 :(得分:1)

是的,这是可能的,你没有做太多, 当您打印数据时,服务器将发送它,只是为了确保保持刷新标准输出

答案 2 :(得分:1)

有一些技巧。

老式的方法是继续流式传输数据,并让浏览器继续使用渐进式渲染来渲染它。因此,作为一个老式的CGI,只需做sys.stdout.flush()。这显示了一个可以继续添加的部分页面,但它在浏览器中看起来很笨拙,因为throbber会继续旋转,看起来很像服务器挂起或过载。

某些浏览器支持特殊的多部分mimetype multipart/x-mixed-replace,它允许您执行保持连接打开的相同技巧,但是当您发送下一个多部分块(必须是MIME-)时,浏览器将完全替换页面格式)。我不知道这是否可用 - Internet Explorer不支持它,它也可能在其他浏览器中不能正常工作。

下一个最现代的方法是使用Javascript XMLHttpRequest轮询服务器的结果。这要求您可以从不同的Web服务器线程或进程检查操作的结果,这在服务器端代码中实现起来要困难得多。它允许您创建更好的网页。

如果您想变得更复杂,请查看“Comet”模型或“Web Sockets”。

答案 3 :(得分:1)

老式CGI程序中的诀窍是使用Transfer-Encoding: chunked HTTP标头:

  

3.6.1分块传输编码

     

分块编码修改消息正文,以便将其作为一系列块传输,每个块都有自己的大小指示符,然后是包含实体标题字段的可选预告片。这允许动态生成的内容与接收方验证其已收到完整消息所需的信息一起传输。

当结果可用时,将其作为单独的块发送 - 浏览器将显示此自包含的HTTP消息。当另一个块稍后到达时,会显示 NEW PAGE

您必须为CGI程序中的每个块生成正确的标头。另外,请记住在每个块的末尾刷新CGI输出。在Python中,这是通过sys.stdout .flush()

完成的