#php #jquery #http-headers
#php #jquery #http-заголовки
Вопрос:
У меня есть очень большой текстовый файл журнала, который мне нужно передать в браузер. У меня есть PHP-приложение, работающее в фоновом режиме, которое добавляет информацию в файл журнала по мере выполнения своих функций. Я хотел бы, чтобы поддержка браузера просматривала журнал в режиме реального времени. Ожидаемое поведение заключается в том, что при первой загрузке он захватывает все содержимое файла, затем перезагружается со следующими 30 КБ и повторяется. У меня выполнен первоначальный запрос, но как только он пытается захватить следующие 30 КБ, я получаю следующую ошибку:
An error occured :-(.
Reloading may help; no promises.
Uncaught Invalid integer accept-ranges: bytes
cache-control: public, must-revalidate, max-age=0
content-disposition: inline; filename=log.txt
content-length: 5747975
content-range: bytes 0-5747975/5747976
content-transfer-encoding: binary
content-type: text/plain;charset=UTF-8
date: Fri, 27 Nov 2020 17:46:54 GMT
last-modified: Fri, 27 Nov 2020 17:39:28 0000
pragma: no-cache
server: LiteSpeed
status: 206
x-litespeed-cache-control: no-cache
x-powered-by: PHP/7.4.12
Это потоковая передача txt-файла на стороне PHP (https://www.php.net/manual/en/function.readfile.php#86244 ):
function smartReadFile($location, $filename, $mimeType='application/octet-stream')
{
$size=filesize($location);
$time=date('r',filemtime($location));
if(!file_exists($location)) { header ("HTTP/1.0 404 Not Found");
return;
}
$fm=@fopen($location,'rb');
if(!$fm)
{ header ("HTTP/1.0 505 Internal server error");
return;
}
$begin=0;
$end=$size;
if(isset($_SERVER['HTTP_RANGE']))
{ if(preg_match('/bytes=h*(d )-(d*)[D.*]?/i', $_SERVER['HTTP_RANGE'], $matches))
{ $begin=intval($matches[0]);
if(!empty($matches[1]))
$end=intval($matches[1]);
}
}
if($begin>0||$end<$size)
header('HTTP/1.0 206 Partial Content');
else
header('HTTP/1.0 200 OK');
header("Content-Type: $mimeType");
header('Cache-Control: public, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Accept-Ranges: bytes');
header('Content-Length:'.($end-$begin));
header("Content-Range: bytes $begin-$end/$size");
header("Content-Disposition: inline; filename=$filename");
header("Content-Transfer-Encoding: binaryn");
header("Last-Modified: $time");
header('Connection: close');
$cur=$begin;
fseek($fm,$begin,0);
while(!feof($fm)amp;amp;$cur<$endamp;amp;(connection_status()==0))
{ print fread($fm,min(1024*16,$end-$cur));
$cur =1024*16;
}
}
Это сторона javascript, выполняющая запрос ajax (https://github.com/ukhas/js-logtail )
(function () {
var dataelem = "#data";
var pausetoggle = "#pause";
var scrollelems = ["html", "body"];
var url = "?mod=log";
var fix_rn = true;
var load = 30 * 1024; /* 30KB */
var poll = 1000; /* 1s */
var kill = false;
var loading = false;
var pause = false;
var reverse = true;
var log_data = "";
var log_file_size = 0;
/* :-( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt */
function parseInt2(value) {
if(!(/^[0-9] $/.test(value))) throw "Invalid integer " value;
var v = Number(value);
if (isNaN(v)) throw "Invalid integer " value;
return v;
}
function get_log() {
if (kill | loading) return;
loading = true;
var range;
var first_load;
var must_get_206;
if (log_file_size === 0) {
/* Get the last 'load' bytes */
range = "-" load.toString();
first_load = true;
must_get_206 = false;
} else {
/* Get the (log_file_size - 1)th byte, onwards. */
range = (log_file_size - 1).toString() "-";
first_load = false;
must_get_206 = log_file_size > 1;
}
/* The "log_file_size - 1" deliberately reloads the last byte, which we already
* have. This is to prevent a 416 "Range unsatisfiable" error: a response
* of length 1 tells us that the file hasn't changed yet. A 416 shows that
* the file has been trucnated */
$.ajax(url, {
dataType: "text",
cache: false,
headers: {Range: "bytes=" range},
success: function (data, s, xhr) {
loading = false;
var content_size;
if (xhr.status === 206) {
var c_r = xhr.getResponseHeader("Content-Range");
if (!c_r)
throw "Server did not respond with a Content-Range";
log_file_size = parseInt2(c_r.split("/")[1]);
content_size = parseInt2(xhr.getAllResponseHeaders("Content-Length"));
} else if (xhr.status === 200) {
if (must_get_206)
throw "Expected 206 Partial Content";
content_size = log_file_size =
parseInt2(xhr.getResponseHeader("Content-Length"));
} else {
throw "Unexpected status " xhr.status;
}
/* amp;amp; data.length > load)
throw "Server's response was too long";*/
var added = false;
if (first_load) {
/* Clip leading part-line if not the whole file */
if (content_size < log_file_size) {
var start = data.indexOf("n");
log_data = data.substring(start 1);
} else {
log_data = data;
}
added = true;
} else {
/* Drop the first byte (see above) */
log_data = data.substring(1);
if (log_data.length > load) {
var start = log_data.indexOf("n", log_data.length - load);
log_data = log_data.substring(start 1);
}
if (data.length > 1)
added = true;
}
if (added)
show_log(added);
setTimeout(get_log, poll);
},
error: function (xhr, s, t) {
loading = false;
if (xhr.status === 416 || xhr.status == 404) {
/* 416: Requested range not satisfiable: log was truncated. */
/* 404: Retry soon, I guess */
log_file_size = 0;
log_data = "";
show_log();
setTimeout(get_log, poll);
} else {
throw "Unknown AJAX Error (status " xhr.status ")";
}
}
});
}
function scroll(where) {
for (var i = 0; i < scrollelems.length; i ) {
var s = $(scrollelems[i]);
if (where === -1)
s.scrollTop(s.height());
else
s.scrollTop(where);
}
}
function show_log() {
if (pause) return;
var t = log_data;
if (reverse) {
var t_a = t.split(/n/g);
t_a.reverse();
if (t_a[0] == "")
t_a.shift();
t = t_a.join("n");
}
if (fix_rn)
t = t.replace(/n/g, "rn");
$(dataelem).text(t);
if (!reverse)
scroll(-1);
}
function error(what) {
kill = true;
$(dataelem).text("An error occured :-(.rn"
"Reloading may help; no promises.rn"
what);
scroll(0);
return false;
}
$(document).ready(function () {
window.onerror = error;
/* If URL is /logtail/?noreverse display in chronological order */
var hash = location.search.replace(/^?/, "");
if (hash == "noreverse")
reverse = false;
/* Add pause toggle */
$(pausetoggle).click(function (e) {
pause = !pause;
$(pausetoggle).text(pause ? "Unpause" : "Pause");
show_log();
e.preventDefault();
});
get_log();
});
})();
Как только он переходит ко второму запросу с 206, я получаю следующую ошибку:
Ответ №1:
Хорошо, я сильно изменил свой код, и иногда проще, чем лучше…
Код Js выглядит следующим образом:
$(function(){
$("#data").load("index.php?mod=logamp;do=refresh");
var auto_refresh = setInterval(
(function () {
$("#data").load("index.php?mod=log");
}), 1000);
function ScrollDiv(){
if(document.getElementById('data').scrollTop<(document.getElementById('data').scrollHeight-document.getElementById('data').offsetHeight)){-1
document.getElementById('data').scrollTop=document.getElementById('data').scrollTop 1
}
else {/*document.getElementById('data').scrollTop=0;*/}
}
setInterval(ScrollDiv, 1)
});