服务器上有数据可用后,立即在网页上显示结果

发布于 2024-08-14 12:08:13 字数 3457 浏览 2 评论 0原文

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

我在这里提出的内容可能吗?我问这个问题是因为以我有限的知识,在cgi页面中,响应基本是一次性发回的,一旦发送响应,cgi页面就会停止运行。这个东西是在服务器端或者客户端做的,我该如何实现呢?

我的服务器正在运行 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

I am writing a cgi page in Python. Let's say a client sends request to my cgi page. My cgi page does the calculation and as soon as it has the first output, it sends back that output to the client, but it will CONTINUE to do the calculation and send other responses AFTER the first response is sent.

Is what I have presented here possible? I ask this question because in my limited knowledge, in a cgi page responses are sent back on one-time basic, once a response is sent, cgi-page stops running. This thing is made on server side or client side, and how do I implement it?

My server is running Apache. Thank you very much.

I have tried a client code from "dbr" in this forum (thanks to him I got the idea of how long-polling works).

<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>

And here is my server code:

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>"

I am expecting my client page to display 1 number at a time (0,1,2,...), but the data always comes out all at once (01234...). Please help me figure it out. Thanks you guys so much.

Just a little out-track, I am trying to use jquery comet plugin, but I couldn't find sufficient documentation though. Helps would be much appreciated. Thanks again :D

[edit] Ok guys, finally thanks to your guides I have managed to make it work. You're right when predict that mod_deflate is the source of all this.

To sum up, what I have done here:

  • For client, make a long poll page as the html code above

  • For server, disable the mod_deflate by: editing file /etc/apache2/mods-available/deflate.conf, comment out the line with text/html part and restart the server. To ensure that Python doesn't buffer the output itself, include #!/usr/bin/python -u in the beginning of the page. Remember to use sys.stdout.flush() after each printing that you want to appear at the client. The effect may not be transparent, should include time.sleep(1) to test. :D

Thanks you guys very much for supporting and helping solving this :D

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

凹づ凸ル 2024-08-21 12:08:13

当然。

有传统的服务器驱动方法,其中脚本只运行一次,但需要很长时间才能完成,并在运行过程中吐出页面的位:

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 时,这是通过让应用程序返回一个可迭代对象来完成的,该可迭代对象输出它想要的每个块一次单独发送一个。我真的建议写信给 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 等输出转换过滤器来自动压缩 Web 应用程序输出,通常会发生这种情况。您需要关闭部分响应生成脚本的压缩。

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

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 请求之间传递数据。管道或数据库。

Sure.

There's traditional server-driven approach, where the script runs just once, but takes a long time to complete, spitting out bits of page as it goes:

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)

When writing an app to WSGI, this is done by having the application return an iterable which outputs each block it wants sent separately one at a time. I'd really recommend writing to WSGI; you can deploy it through CGI now, but in the future when your app needs better performance you can deploy it through a faster server/interface without having to rewrite.

WSGI-over-CGI example:

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)

Note that your web server may foil this approach (for CGI or WSGI) by adding buffering of its own. This typically happens if you're using output-transforming filters like mod_deflate to automatically compress webapp output. You'll need to turn compression off for partial-response-generating scripts.

This limits you to rendering the page bit-by-bit as new data comes in. You can make it prettier by having the client-side take care of altering the page as new data comes in, eg.:

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)

This relies on client-side scripting so it might be a good idea to include backup non-script-based final output at the end.

All the while doing this, the page will appear to be still loading. If you don't want that, then you'd need to split the script into a first request that just spits out the static content, including client-side script that checks back with the server using either one XMLHttpRequest that it polls for new data through, or, for the really long-running cases, many XMLHttpRequests each of which returns the status and any new data. This approach is much more complicated as it means you have to run your work process as a background daemon process apart from the web server, and pass data between the daemon and the front-end CGI/WSGI request using eg. pipes or a database.

野心澎湃 2024-08-21 12:08:13

是的,这是可能的,而且你不需要做太多事情,
当您打印数据时,服务器将发送它,只是为了确保不断刷新标准输出

Yes thats possible and you don't have do much,
as you print data out, server will send it, just to be sure keep flushing stdout

快乐很简单 2024-08-21 12:08:13

有一些技巧。

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

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

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

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

There are a few techniques.

The old-fashioned way is to continue to stream data, and have the browser continue to render it using progressive rendering. So as an old-fashioned CGI, just do sys.stdout.flush(). This shows a partial page that you can keep adding to, but it looks clumsy in the browser because the throbber will keep spinning and it looks much like the server is hung or overloaded.

Some browsers support a special multipart mimetype multipart/x-mixed-replace that allows you to do the same trick of keeping the connection open, but the browser will replace the page completely when you send the next multipart chunk (which must be MIME-formatted). I don't know if that's usable - Internet Explorer doesn't support it and it may not work well in other browser either.

The next most modern way is polling the server for results with Javascript's XMLHttpRequest. This requires that you can check the results of the operation from a different webserver thread or process, which can be quite a bit more difficult to achieve in the server-side code. It allows you to create a much nicer web page though.

If you want to get even more complicated, check out the "Comet" model or "Web Sockets".

白馒头 2024-08-21 12:08:13

老式 CGI 程序中的技巧是使用 传输编码:分块 HTTP 标头:

3.6.1 分块传输编码

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

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

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

The trick in old-fashioned CGI programs is using the Transfer-Encoding: chunked HTTP header:

3.6.1 Chunked Transfer Coding

The chunked encoding modifies the body of a message in order to transfer it as a series of chunks, each with its own size indicator, followed by an OPTIONAL trailer containing entity-header fields. This allows dynamically produced content to be transferred along with the information necessary for the recipient to verify that it has received the full message.

When a result is available, send it as a separate chunk - the browser will display this self-contained HTTP message. When another chunk arrives later, a NEW PAGE is displayed.

You'll have to produce the correct headers for each chunk inside the CGI program. Also, remember to flush the CGI output at the end of each chunk. In Python this is done with sys.stdout.flush()

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文