在perl中显示真实的实时进度

时间:2012-11-03 18:27:29

标签: html perl cgi

我在cgi中有这段代码。

#!/usr/bin/perl
print "content-type: text/html \n\n";
print "<html><head><style>div.section{  font-size: 12px;    border: 1px solid #999999;  background-color: #EFEFEF;  margin: 10px 15px;}p{   font-family: \"Arial\", \"Helvetica\", \"sans-serif\";  font-size: 14px;    color: #444444; line-height: 30px;  margin: 0 10px 0 10px;  padding: 0 0 6px 0; text-align: center;}</style></head><body><div class=\"section\"><p>";
print "<b>YAMJ successfully updated your movie collection</b>";
print "</p><b>YAMJ output:</b><br>";
print "<pre>";
my $status = system("/usr/local/YAMJ/run.sh");
print "</pre>";
print "</div></body></html>";

该代码由MissileHugger为Synology中的YAMJ创建。事情是每次用户在synology上打开应用程序时,它只会在脚本执行其操作时为您提供空白页。

我想知道,有没有办法输出当时脚本正在做什么的实时数据进度?与我们安装Windows软件时类似,状态显示正在复制的文件等。

任何人都知道我是否可以这样做?

抱歉我的英语很差。

3 个答案:

答案 0 :(得分:1)

这不容易做到。原因:HTTP是无状态的,浏览器只有在完全加载后才会呈现页面。

   SERVER     COMMUNICATION  CLIENT
+------------+              +---------+
| Rendezvous | <----------- | Client  |
+------------+  get status  | Browser |
    ^              via      |         |
    | write     javascript  |         |
    | status                |         |
+------------+    invoke    |         |
|   Process  | <----------- |         |
+------------+              +---------+

以下是您可以使用的算法:

  1. 客户端浏览器请求CGI脚本。这开始了一个漫长的过程。它会向浏览器返回唯一的会话ID

  2. 长时间运行的进程会不断重写具有其状态的集合文件。

  3. 客户端浏览器轮询ID指定的rendezvous文件,并使用javascript更新网页。 HTML5有一个<progress> meter元素,您可以使用它。

  4. 会话结束后,文件将从服务器文件系统中删除。

  5. 在最简单的情况下,您将浏览器指向一个连续重写的页面,并指示浏览器每隔几秒重新加载该页面。在AJAX时代,这基本上已经过时了。通过javascript加载内容将更加顺畅,并允许通过JSON传输复杂状态,例如:

    { "status": 0.4,
      "comments": ["loaded a file",
                   "doing really hard math",
                   "almost finished - just a moment please"]}
    

    而不是纯文本

    Status: 40%
    

    让服务器将这些事件推送到浏览器会非常优雅,但是,我不知道如何做到这一点。

答案 1 :(得分:0)

很棒的代码。非常感谢你的解决方案。我在过去的3天里一直在研究这个问题..

这是我更新脚本以使用我的程序所做的。

#!/usr/bin/perl 

$|++; #This is needed to disable buffering

print "Content-type: text/html\r\n";
# This header is a quick hack which denies browser to gzip/deflate your output
print "Content-Encoding: plain\r\n\r\n";

&load_buffer; #This keeps the browser window printing progress as it's running

从这一点开始,任何print语句都将在程序运行时起作用。

示例测试

for ($i = 1; $i < 11; ++$i)

{
print "$i<br>\n";

sleep(1);

}

子程序

sub load_buffer {

my $cmd = qq{perl -e "\$|++; print ' ' and sleep(1) for 1..1"};
open my $PROC, '-|', $cmd or die $!;
while (sysread $PROC, $buffer, 1) { print "<!-- Browser shoudln't      cache this chunk ", ' ' x 1000, "-->\n";} 

}

答案 2 :(得分:-1)

您需要启用STDOUT文件句柄的自动刷新才能实现此目的。 基本上,将以下代码放在脚本的顶部(在shebang之后)应该有所帮助:

$|++;

请访问previous answers on autoflush question了解有关autoflush的详细信息。

以下oneliners可以向您展示不同之处:

perl -e "$|++; print '#' and sleep(1) for 1..10"
perl -e "print '#' and sleep(1) for 1..10"

P.S。:如果你在终端中运行/usr/local/YAMJ/run.sh,我想你可以看到ASCII进度条。

UPD:

这应该有效,但是:

  1. Apache可以gzip您的输出并作为单个块发送,如果启用了压缩
  2. 浏览器(例如Chrome)缓冲输出。
  3. 因此,您必须执行一些额外的工作:

    #!/usr/bin/perl
    
    $|++;
    
    print "Content-type: text/html\r\n";
    # This header is a quick hack which denies browser to gzip/deflate your output
    print "Content-Encoding: plain\r\n\r\n";
    
    print "<html><body><h1>Start</h1>\n";
    
    my $cmd = qq{perl -e "\$|++; print '#' and sleep(1) for 1..100"};
    
    # Start your command, read it's output byte by byte
    # and send it to the browser with a 1000+ bytes long comment
    # which should prevent browser from caching just a single character
    open my $PROC, '-|', $cmd or die $!;
    
    my $buffer;
    while (sysread $PROC, $buffer, 1) {
        print $buffer;
        print "<!-- Browser shoudln't cache this chunk ", ' ' x 1000, "-->\n";
    }
    
    print "<h1>Done</h1></body></html>";