- android - RelativeLayout 背景可绘制重叠内容
- android - 如何链接 cpufeatures lib 以获取 native android 库?
- java - OnItemClickListener 不起作用,但 OnLongItemClickListener 在自定义 ListView 中起作用
- java - Android 文件转字符串
问题:
如何在服务器和客户端不阻塞的情况下实时读取和回显正在写入服务器的上传文件的文件大小?
上下文:
从 fetch()
发出的 POST
请求写入服务器的文件上传进度,其中 body
设置为 Blob
、File
、TypedArray
或 ArrayBuffer
对象。
当前的实现在 body
对象处设置 File
对象传递给 fetch()
的第二个参数。
要求:
读取并回显
以text/event-stream
形式向客户端写入正在服务器文件系统中的文件的文件大小。当所有字节都被写入时停止,这些字节作为变量提供给脚本作为 GET
请求中的查询字符串参数。当前文件的读取发生在一个单独的脚本环境中,其中对应该读取文件的脚本的 GET
调用是在对将文件写入服务器的脚本的 POST
之后进行的。
尚未达到对将文件写入服务器或读取文件以获得当前文件大小的潜在问题的错误处理,尽管一旦文件大小部分的 echo
完成,这将是下一步。
目前正在尝试使用 php
来满足要求。虽然也对 c
、bash
、nodejs
、python
感兴趣;或可用于执行相同任务的其他语言或方法。
客户端 javascript
部分不是问题。只是不太精通 php
(万维网上使用的最常见的服务器端语言之一),无法在不包含不必要部分的情况下实现该模式。
动机:
Progress indicators for fetch?
相关:
问题:
获取
PHP Notice: Undefined index: HTTP_LAST_EVENT_ID in stream.php on line 7
在终端
。
另外,如果替换
while(file_exists($_GET["filename"])
&& filesize($_GET["filename"]) < intval($_GET["filesize"]))
对于
while(true)
在 EventSource
处产生错误。
没有 sleep()
调用,正确的文件大小被分派(dispatch)给 3.3MB
文件的 message
事件,3321824
, 分别在console
61921
, 26214
, 和 38093
打印同一个文件三个次。预期结果是文件的文件大小,因为文件正在写入
stream_copy_to_stream($input, $file);
而不是上传文件对象的文件大小。 fopen()
或 stream_copy_to_stream()
是否阻塞了 stream.php
中的其他不同的 php
进程?
到目前为止尝试过:
php
归于
php
// can we merge `data.php`, `stream.php` to same file?
// can we use `STREAM_NOTIFY_PROGRESS`
// "Indicates current progress of the stream transfer
// in bytes_transferred and possibly bytes_max as well" to read bytes?
// do we need to call `stream_set_blocking` to `false`
// data.php
<?php
$filename = $_SERVER["HTTP_X_FILENAME"];
$input = fopen("php://input", "rb");
$file = fopen($filename, "wb");
stream_copy_to_stream($input, $file);
fclose($input);
fclose($file);
echo "upload of " . $filename . " successful";
?>
// stream.php
<?php
header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");
header("Connection: keep-alive");
// `PHP Notice: Undefined index: HTTP_LAST_EVENT_ID in stream.php on line 7` ?
$lastId = $_SERVER["HTTP_LAST_EVENT_ID"] || 0;
if (isset($lastId) && !empty($lastId) && is_numeric($lastId)) {
$lastId = intval($lastId);
$lastId++;
}
// else {
// $lastId = 0;
// }
// while current file size read is less than or equal to
// `$_GET["filesize"]` of `$_GET["filename"]`
// how to loop only when above is `true`
while (true) {
$upload = $_GET["filename"];
// is this the correct function and variable to use
// to get written bytes of `stream_copy_to_stream($input, $file);`?
$data = filesize($upload);
// $data = $_GET["filename"] . " " . $_GET["filesize"];
if ($data) {
sendMessage($lastId, $data);
$lastId++;
}
// else {
// close stream
// }
// not necessary here, though without thousands of `message` events
// will be dispatched
// sleep(1);
}
function sendMessage($id, $data) {
echo "id: $id\n";
echo "data: $data\n\n";
ob_flush();
flush();
}
?>
javascript
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<input type="file">
<progress value="0" max="0" step="1"></progress>
<script>
const [url, stream, header] = ["data.php", "stream.php", "x-filename"];
const [input, progress, handleFile] = [
document.querySelector("input[type=file]")
, document.querySelector("progress")
, (event) => {
const [file] = input.files;
const [{size:filesize, name:filename}, headers, params] = [
file, new Headers(), new URLSearchParams()
];
// set `filename`, `filesize` as search parameters for `stream` URL
Object.entries({filename, filesize})
.forEach(([...props]) => params.append.apply(params, props));
// set header for `POST`
headers.append(header, filename);
// reset `progress.value` set `progress.max` to `filesize`
[progress.value, progress.max] = [0, filesize];
const [request, source] = [
new Request(url, {
method:"POST", headers:headers, body:file
})
// https://stackoverflow.com/a/42330433/
, new EventSource(`${stream}?${params.toString()}`)
];
source.addEventListener("message", (e) => {
// update `progress` here,
// call `.close()` when `e.data === filesize`
// `progress.value = e.data`, should be this simple
console.log(e.data, e.lastEventId);
}, true);
source.addEventListener("open", (e) => {
console.log("fetch upload progress open");
}, true);
source.addEventListener("error", (e) => {
console.error("fetch upload progress error");
}, true);
// sanity check for tests,
// we don't need `source` when `e.data === filesize`;
// we could call `.close()` within `message` event handler
setTimeout(() => source.close(), 30000);
// we don't need `source' to be in `Promise` chain,
// though we could resolve if `e.data === filesize`
// before `response`, then wait for `.text()`; etc.
// TODO: if and where to merge or branch `EventSource`,
// `fetch` to single or two `Promise` chains
const upload = fetch(request);
upload
.then(response => response.text())
.then(res => console.log(res))
.catch(err => console.error(err));
}
];
input.addEventListener("change", handleFile, true);
</script>
</body>
</html>
最佳答案
您需要clearstatcache获得真实的文件大小。修复了一些其他位后,您的 stream.php 可能如下所示:
<?php
header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");
header("Connection: keep-alive");
// Check if the header's been sent to avoid `PHP Notice: Undefined index: HTTP_LAST_EVENT_ID in stream.php on line `
// php 7+
//$lastId = $_SERVER["HTTP_LAST_EVENT_ID"] ?? 0;
// php < 7
$lastId = isset($_SERVER["HTTP_LAST_EVENT_ID"]) ? intval($_SERVER["HTTP_LAST_EVENT_ID"]) : 0;
$upload = $_GET["filename"];
$data = 0;
// if file already exists, its initial size can be bigger than the new one, so we need to ignore it
$wasLess = $lastId != 0;
while ($data < $_GET["filesize"] || !$wasLess) {
// system calls are expensive and are being cached with assumption that in most cases file stats do not change often
// so we clear cache to get most up to date data
clearstatcache(true, $upload);
$data = filesize($upload);
$wasLess |= $data < $_GET["filesize"];
// don't send stale filesize
if ($wasLess) {
sendMessage($lastId, $data);
$lastId++;
}
// not necessary here, though without thousands of `message` events will be dispatched
//sleep(1);
// millions on poor connection and large files. 1 second might be too much, but 50 messages a second must be okay
usleep(20000);
}
function sendMessage($id, $data)
{
echo "id: $id\n";
echo "data: $data\n\n";
ob_flush();
// no need to flush(). It adds content length of the chunk to the stream
// flush();
}
一些注意事项:
安全。我的意思是运气。据我所知,这是一个概念证明,安全是最不关心的问题,但免责声明应该在那里。这种方法存在根本性缺陷,只有在您不关心 DOS 攻击或文件信息泄露时才应使用。
中央处理器。如果没有 usleep
,脚本将消耗 100% 的单个内核。如果长时间休眠,您将面临在一次迭代中上传整个文件的风险,并且永远不会满足退出条件。如果你在本地测试它,usleep
应该完全删除,因为在本地上传 MB 是毫秒级的事情。
打开连接。 apache 和 nginx/fpm 都有有限数量的 php 进程可以满足请求。单个文件上传将花费 2 为上传文件所需的时间。如果带宽较慢或伪造请求,这段时间可能会很长,并且 Web 服务器可能会开始拒绝请求。
客户端部分。您需要分析响应并最终在文件完全上传时停止监听事件。
编辑:
为了使它或多或少对生产友好,您将需要一个内存存储,如 Redis 或 Memcache 来存储文件元数据。
发出一个 post 请求,添加一个唯一的 token 来标识文件和文件大小。
在你的 javascript 中:
const fileId = Math.random().toString(36).substr(2); // or anything more unique
...
const [request, source] = [
new Request(`${url}?fileId=${fileId}&size=${filesize}`, {
method:"POST", headers:headers, body:file
})
, new EventSource(`${stream}?fileId=${fileId}`)
];
....
在data.php中注册token并分块上报进度:
....
$fileId = $_GET['fileId'];
$fileSize = $_GET['size'];
setUnique($fileId, 0, $fileSize);
while ($uploaded = stream_copy_to_stream($input, $file, 1024)) {
updateProgress($id, $uploaded);
}
....
/**
* Check if Id is unique, and store processed as 0, and full_size as $size
* Set reasonable TTL for the key, e.g. 1hr
*
* @param string $id
* @param int $size
* @throws Exception if id is not unique
*/
function setUnique($id, $size) {
// implement with your storage of choice
}
/**
* Updates uploaded size for the given file
*
* @param string $id
* @param int $processed
*/
function updateProgress($id, $processed) {
// implement with your storage of choice
}
所以你的 stream.php 根本不需要打盘,只要 UX 可以接受就可以休眠:
....
list($progress, $size) = getProgress('non_existing_key_to_init_default_values');
$lastId = 0;
while ($progress < $size) {
list($progress, $size) = getProgress($_GET["fileId"]);
sendMessage($lastId, $progress);
$lastId++;
sleep(1);
}
.....
/**
* Get progress of the file upload.
* If id is not there yet, returns [0, PHP_INT_MAX]
*
* @param $id
* @return array $bytesUploaded, $fileSize
*/
function getProgress($id) {
// implement with your storage of choice
}
2个open connections的问题无法解决,除非你放弃EventSource换回old good pulling。没有循环的 stream.php 的响应时间是毫秒级的事情,一直保持连接打开是相当浪费的,除非你需要每秒更新数百次。
关于javascript - 如何在不阻塞服务器和客户端的情况下实时读取和回显在服务器上写入的上传文件的文件大小?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42475492/
"); ..... 回显 (""); ?> 是什么意思?
问题是标签 我看过我正在编写的一个脚本,并使用了它: echo (""); ........ echo (""); 它到底做了什么? 它是 Html 标签还是 PHP? 我在 Google 上进行了
我正在创建一个结帐系统。我分为三个部分: 送货信息 付款信息 订单确认 我正在尝试找出一种方法,当客户输入他们的送货信息时,该数据可以回显到我的订单确认部分,这样他们就可以确认这是他们想要将其运送到的
我正在 MYSQL 表上运行 SELECT 代码 我有一个 MYSQL 表: CREATE TABLE mytable ( wf_id int(11) UNSIGNED NOT NULL, sessi
我想要做的是回显页面顶部的内容,同时捕捉页面底部的内容。 这是我的脚本: Westpop Wachtwoord moet minstens 8 tekens lang
我在将选择列表放在 HTML 中时遇到了一些困难,其中的选项或值是从数据库中的表中检索的。我得到以下结果: 如您所见,这不是我的本意,我宁愿将所有选项都放在 1 个选择列表中,并且可以单击多个选项。
我的代码有问题。我有这段代码: Title Welcome loggedin:
我需要使用 PHP 仅回显 .txt 文件中的选定行。txt 文件内容如下所示: 1 111 111 111 111 111 111 111 111 111 111 2 196 182 227 190
我需要使用 PHP 仅回显 .txt 文件中的选定行。txt 文件内容如下所示: 1 111 111 111 111 111 111 111 111 111 111 2 196 182 227 190
我想从 PHP 回显一些 HTML,其中包含一个链接,上面有文本“ ' '; 最佳答案 如果您想显示文字小于号,请根据 HTML 规范将其编码为 <。 您可能还想查看另一个相关实体:«。这会产生一个
如果我从名为 categories 的 mysql 表中调用链接列表,我想我使用的选择语句如下: $query = "SELECT cat_name, cat_description FROM c
所以,伙计们,我的问题是我正在从一个 mysql 列创建一个数组,但是,当我回显数组项时,它什么也没返回,我有一段时间没有看到一个可能的错误,希望能得到一些帮助。这是我的代码:(我知道 mysql 到
在下面的代码中,我在 php 文件中回显了 $strXML,它显示了整个 $strXML,但我只想在 javascript 中显示“名称”元素值。谁能帮帮我? PHP: $strXML = ''."\
我有一节课用这段代码 public boolean busybox() throws IOException { try { Process p =Ru
我有一个 SQL 查询,出于安全目的,我最近将其转换为准备好的语句。我有一个查询返回许多行,每行由许多列组成。我的问题是如何使用 while 循环回显结果。到目前为止我的例子: $stmt = $co
我尝试这样的事情: function userrank($userid){ $sql = mysql_query("SELECT * FROM users ORDER BY atacs DES
我正在尝试进行选择查询并将此信息输出到 html 标签中,但是它不断将 php 代码转换为: 我做错了什么?为什么它不返回错误而不是将其转换为上面的错误? 代码 $con = mysqli_conn
抱歉含糊其辞,但我有一个 PHP 脚本,它从数据库中提取数据并显示它,但是它不是输出数据库的内容,而是输出“数组”的次数。 (无法再次工作,脚本已上传 here ),脚本的输出也是 here .)谢谢
我有一个工作代码并将其移动到一个新服务器,从 php5 到 php7。谷歌搜索一周没有帮助我,所以我问你们。 我在 sql 数据库中有一些带有欧元符号的信息: 99.00该信息存储为 utf16-ge
这是我的代码: 0){ $info = mysql_fetch_assoc($result); while($row = mysql_fetch_assoc($result)){
我在查找有关此主题的信息时遇到了一些真正的困难,如果您有任何帮助,我将非常感激。简而言之,我有一个表单,用户可以从下拉列表中选择一个类别,输入一些内容,然后点击提交,然后转到 SQL。下拉列表中的每个
我是一名优秀的程序员,十分优秀!