一旦服务器有数据,如何在网页上显示结果
我正在用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>"
我希望我的客户端页面一次显示一个数字(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 个回答
有几种方法可以做到这一点。
最传统的方式是继续传输数据,让浏览器逐步渲染这些数据。就像老式的CGI那样,你可以使用 sys.stdout.flush()
。这样做会显示一个部分加载的页面,你可以不断添加内容,但在浏览器中看起来会有点笨拙,因为加载指示器会一直转动,看起来就像服务器卡住了或者超负荷了。
有些浏览器支持一种特殊的多部分MIME类型 multipart/x-mixed-replace
,这允许你保持连接打开,但当你发送下一个多部分数据块时,浏览器会完全替换页面(这个数据块必须是MIME格式的)。我不太确定这个方法是否好用——因为Internet Explorer不支持它,其他浏览器可能也不太兼容。
更现代的方法是使用JavaScript的 XMLHttpRequest
来轮询服务器获取结果。这要求你能够从不同的服务器线程或进程中检查操作的结果,这在服务器端代码中可能会比较复杂。不过,这种方法可以让你创建一个更漂亮的网页。
如果你想要更复杂的方案,可以看看“Comet”模型或者“Web Sockets”。
是的,这是可能的,你不需要做太多事情。只要你把数据打印出来,服务器就会发送这些数据。为了确保这一点,记得要定期清空输出缓冲区。
当然可以。
这里有一种传统的服务器驱动的方法,脚本只运行一次,但完成的时间很长,期间会逐步输出页面的部分内容:
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与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)
注意,你的网络服务器可能会干扰这种方法(无论是CGI还是WSGI),因为它可能会自己添加缓冲。这通常发生在你使用像mod_deflate
这样的输出转换过滤器来自动压缩网页应用的输出时。你需要关闭压缩,以便让部分响应生成的脚本正常工作。
这限制了你只能在新数据到来时逐步渲染页面。你可以通过让客户端处理页面的更新来让它看起来更美观,例如:
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,每个请求返回状态和任何新数据。这种方法要复杂得多,因为这意味着你需要将工作过程作为一个后台守护进程运行,而不是直接在网络服务器上,并通过例如管道或数据库在守护进程和前端CGI/WSGI请求之间传递数据。