PHP 远程文件下载的进度条实现

原文地址:https://prinzeugen.net/implem...

PHP 实现远程下载文件到服务端并不是什么新鲜玩意,用 cURLfile_get_contentsfopen 等都能够轻易实现。

但是这几种常规的方法都是在一个线程内下载文件,等文件下载完毕以后才能返回 HTTP 响应。所造成的结果就是用户在页面上点击「下载到服务器」按钮后,会看到空白页和加载的小菊花转啊转,转好久之后才出现「下载成功」的页面。

当然,我上面所举例的情况是只使用纯粹的表单 POST 发送请求的情况。现在的话就算再不济也一般会使用 ajax 发送请求,然后在前台放个加载动画,等收到下载成功的回应之后再进行下一步操作。

但是!即使是去掉了恶心的且需要等待的空白页,这样做还是对用户体验有不好的影响。没有具体的下载进度,只有一个一直转呀转的菊花图,估计挺多用户都无法坐和放宽吧(至少对于我来说是这样的)

而我一个 PHP 项目的一键更新系统正好需要重构,遂研究了如何在 PHP 作为后端时显示远程文件下载进度条,并捣鼓出了个像样的解决方案,在这里分享一下。


0x01 原理

也许你在搜索「PHP 下载 进度条」的时候会看到有些文章使用 PHP 的输出控制函数(flush 之类的)控制缓冲区来实现进度条。但是——

那都是狗屁!

没有人可以保证用户的 PHP 关闭了默认开启的 output buffering,也无法保证 浏览器 / Web Server 不对脚本输出进行缓存。如果上述两者其中之一处于开启状态的话,你就会喜闻乐见的发现本应该慢慢增长的进度条会在等待完漫长的 xx 秒后一下子蹦到 100%_(因为控制前端进度条长度的语句被缓存起来,在脚本执行结束后一并发送了,而不是一块一块地传给浏览器)_。

关于上面缓冲区控制的进度条就是辣鸡的更多讨论可以查看文章底部的参考链接。

闲话休提。那么我们该如何实现下载进度条的更新呢?

首先通过后端一点点输出控制进度条语句的方案已经 PASS 了,那么我们很自然的就会想到——

在前端设置一个定时器,Ajax 轮循下载进度并更新页面上的进度条。

0x02 概述

知道了原理之后,我们先来考虑下整体的架构与步骤。

  1. 用户点击「下载」按钮,前端展示出进度条,并 ajax 发送 prepare-download 的请求;

  2. 后端收到请求,进行远程下载的准备工作 —— 准备好远程文件链接、临时文件存放位置以及文件的大小,并返回给浏览器;

  3. 前端拿到文件大小等信息后,发送真正的 start-download 请求(这个请求耗时可能会很长),并启动轮循的计时器

  4. 计时器启动后,每隔一段时间发送 get-file-size 请求,获取当前临时文件的大小,计算进度后更新进度条;

  5. 直到下载完成。

下面给出前后端代码的实例。

0x03 后端

代码照例放在 Gist 上,加载不出自行解决:

https://gist.github.com/print...

示例代码使用了 ?action=xxx 的 Query String 形式来区分不同的指令,这些请酌情修改。和我业务逻辑有关的一些关键函数都被我替换为浅显易懂的名字(譬如 get_remote_file_url)了,需要你自己去替换实现。

0x04 前端

https://gist.github.com/print...

0x05 效果 & 总结

效果图

实例代码用了 fopen 和循环 fwrite 写入一个 chunk 的数据到临时文件,这是借鉴了 KODExplorer 远程下载的函数,在此致谢。另外也有通过 curl_setopt($ch, CURLOPT_FILE, $fp); 给 cURL 设置一个文件句柄的方法,但是我没有测试成功,希望各位也能试一试。

以上。

参考链接

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于实现文件下载进度条的问题,其实可以通过在后端控制下载时的数据流来获取进度信息,并且将进度信息传递到前端页面以实现进度条的展示。具体来说,可以在后端使用Django框架内置的FileResponse类以及StreamingHttpResponse类进行文件下载,并且使用StreamingHttpResponse类的iterator()方法来逐步获取下载文件的数据流,并通过计算数据流大小以及已传输的数据大小来确定下载进度,并将进度信息传递到前端进行展示。例如,可以通过在后端代码中写入如下代码来实现文件下载进度条: ``` python from django.http import HttpResponse, StreamingHttpResponse import os def file_download(request): file_name = 'your_file_name_here' file_path = 'your_file_path_here' response = StreamingHttpResponse(file_iterator(file_path)) response['Content-Disposition'] = 'attachment; filename="{0}"'.format(file_name) return response def file_iterator(file_path, chunk_size=512): with open(file_path, 'rb') as f: while True: c = f.read(chunk_size) if c: yield c else: break ``` 在以上代码中,file_download()函数用于实现文件下载功能,file_iterator()函数用于逐步获取文件数据流,并且通过StreamingHttpResponse类的iterator()方法返回的数据流传输数据。同时,我们可以在file_iterator()函数中添加一些代码来获取文件大小、已传输数据大小以及当前下载进度,并将进度信息传递到前端进行展示,从而实现文件下载进度条的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值