如何使用AJAX显示PHP curl进度

4
我正在使用 CakePHP 框架(v2.x)编写一个 PHP 网页应用程序。在我的一个视图中,有一个表单,可以将大型视频文件上传到第三方 API。这些文件很大,所以上传可能需要一两分钟的时间。因此,我使用 AJAX 提交表单,并向用户显示跳舞的河马(或其他东西),以便他们知道进度仍在进行中。以下是使用 jquery form plugin 的该脚本:
<script>
var options = {
    //complete : callback_function...,
    //error : callback_function...,
    beforeSend : function() {
        $("#MediaSubmitForm").hide();
        $("#MediaSubmitForm").after('<img class="hula-hippo" src="/img/hippo-hula.gif" />');

    },
    success : function(data){
        $(".hula-hippo").hide();
        $("#MediaSubmitForm").after("<h3>Upload complete!</h3>");
        console.log(data);
    },
    uploadProgress: function(event, position, total, percentComplete){
        console.log(percentComplete);
    }

};
$("#MediaSubmitForm").ajaxForm(options);
</script>

为了不向客户端公开我们的API密钥,我将表单提交到一个控制器操作,该操作使用curl从服务器实际向API进行POST请求。这是该方法(以及一个回调函数)。
//uploads a video to a project through the API
public function apiUpload( $tmp_filename, $project_hashed_id ) {
    $api_password = 'xxxxxxxxxx';
    $username = 'api';
    $data = array(
        'api_password'  => $api_password,
        'file'          => '@'.$tmp_filename,
        'project_id'    => $project_hashed_id
        );

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_URL, "https://upload.wistia.com" );
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);    
    curl_setopt($ch, CURLOPT_NOPROGRESS, FALSE);    
    curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, array($this, 'apiUploadCallback'));  
    $result = curl_exec($ch); //***Note A***
    curl_close($ch);
    return( json_decode($result) );

}

//BE CAREFUL... PHP 5.5 Added the cURL resource as the first argument to the CURLOPT_PROGRESSFUNCTION callback 
protected function apiUploadCallback( $total_bytes_down_expected, $bytes_down_so_far, $total_bytes_up_expected, $total_bytes_up_so_far ) {
    if ($total_bytes_up_expected > 0)
        //error_log( round( ($total_bytes_up_so_far / $total_bytes_up_expected)*100) );
        echo ( round( ($total_bytes_up_so_far / $total_bytes_up_expected)*100) ); //***Note B***
}

这个可以工作,但如果我能向用户展示上传的实际进度,那将会更好。我已经想出如何通过回调函数计算进度(见***注释B***),但我不确定如何将其传递回页面(用户正在等待的页面)。我尝试使用jQuery的uploadProgress,但它显示的是文件上传到我们服务器的进度...而不是上传到API的进度。我尝试从curl回调中回显进度(***注释B***),但它会输出一系列数字,与API返回的JSON对象(***注释A***)纠缠在一起,而我也需要该JSON对象。类似于这样:
11112222233344444...9898989899999999999999{api:"response mixed in here"}100100100100

如何捕获curl进度和后续API响应?

你能否再发一个ajax调用来检查进度?使用超时来反复轮询进度,直到完成。 - Egg Vans
这是一个有趣的想法,但我想听听你更多的想法。例如,您是否考虑使用两个ajax调用到不同的位置?还是两个ajax调用到同一个控制器...第一个处理进度,第二个查找json对象。 - emersonthis
我在考虑调用不同控制器的两个调用(实话说,我对Cake不熟悉),您可以将进度存储在会话变量中,并使用setTimeout进行轮询,直到进度达到100%。 - Egg Vans
关于将进度存储在会话变量中的想法很有趣。我猜上传进度回调函数只需重复地对其进行检测,直到完成为止。我可能会尝试一下这个方法。 - emersonthis
1
我尝试将进展保存在会话变量中。长话短说:这非常棘手,因为PHP锁定了会话文件。因此,同时访问被排队,而试图读取会话的第二次调用必须等到下载完成后才能继续进行(这是徒劳)。显然,锁定行为可以被操纵,但我还没有弄清楚。 - emersonthis
请问您如何在jQuery中显示进度条? - Ashish
3个回答

2
您可以将进度状态存储在用户的会话中,然后使用ajax从会话中获取信息并在页面上显示。

1
请查看我在上面问题下方的评论。 - emersonthis

0

是的,正如Moshen所说:

您可以将进度状态存储在用户会话中,然后使用ajax从会话中获取信息,然后在页面上显示它

但不要忘记调用'session_write_close()'

$nPourcent = round(($total_bytes_up_so_far / $total_bytes_up_expected)*100);

session_start();
$_SESSION['progress'] = $nPourcent;
session_write_close();

0
你可以使用AJAX在服务器上检查状态,并让它每隔一段时间调用一次。但这会不必要地占用你的服务器,因为每次都需要一个新的HTTP请求。我建议你使用Web Socket。这样,你就可以直接在服务器和浏览器之间建立连接,当进度更新时,服务器可以直接向浏览器发送更新。

Web sockets最终将成为这种问题的解决方案,但浏览器支持还不够,我需要一些可以立即投入生产的东西。 - emersonthis
@Emerson 目前,所有浏览器的最新版本都有支持。链接 当然,如果浏览器不支持,你可以为其提供备用方案,然后使用AJAX代替。 - Hugo
谢谢,雨果。我们正在构建的项目必须支持IE8+以及其他几个版本。所以我想后续问题是:备用方案是什么? - emersonthis
我会让apiUploadCallback函数将进度写入一个文件,其中文件名对应于用户的会话。让用户的浏览器向您的服务器发出AJAX调用到一个PHP文件来读取该文件,或者直接访问该文件并让JavaScript完成其余部分。 - Hugo
我实际尝试过这个方法,它确实有效。但是(对于任何想要尝试的人),你必须记住一件重要的事情:进度文件需要对于每个用户都是唯一的。否则,同时有多个用户将会看到其他用户的疯狂数据。我采用的方法是创建/前缀文件名与当前用户会话ID。您还应该记得在完成后删除该文件,以免最终拥有数百万个进度日志文件。我不知道这种方法在生产中是否稳定。 - emersonthis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接