PHP. Хедеры, кэш, jpeg.
На хостинг, теоритически раз в секунду, грузится jpg, постоянно перезаписывается с темже именем.
Клиент грузит хтмл, где яваскрипт обращается к пхп-сценарию, который получает ETag и выбрасывает ему содержимое jpeg'a, если Etag не совпадает.
Вот коды:
клиентский хтмл
<HEAD>
<script language="Javascript">
<!--
var refreshtime=1000;
function refreshCam(){
document.images["webcam"].src = "test.php"; setTimeout("refreshCam()", refreshtime)
}
//-->
</script>
</head>
<body>
<img src="test.php" name="webcam" border="0" alt="Изображение загружается..."></center>
<script language="JavaScript">
<!--
if( document.images )
refreshCam();
//-->
</script>
</body>
серверный пхп
$filename = ("testcam.jpg");
$fsize=filesize($filename);
$ftime=filemtime($filename);
$request=$_SERVER['HTTP_IF_NONE_MATCH'];
$tag=(fileinode($filename)."-".$fsize."-".$ftime);
if (($request==$tag) || ($fsize<4000)) {
header("HTTP/1.1 304 Not Modified");
echo "Cached!";
exit();
}
header("Content-Type: image/jpg");
header( "Cache-Control: must-revalidate" );
header("ETag: ".$tag);
header("Last-Modified: ".$ftime);
readfile($filename);
exit();
?>
Вопросов два:
1. этот JS-рефреш работает только в Firefox'e(в опере не пробовал), IE сам не обнавляет картинку, хотя буквально с утра все на нем работало.
2. Он же главный. Переодически при срабатывании рефреша, изображение не загружается, а показывается его alt, с чем это может быть связано? Почему браузер не хватает картинку из кэша, если не может ее загрузить с сервера? Хочу сделать так, чтобы отображалось последнее кэшированное изображение, до тех пор пока не загрузится новейшее изображение с сервера.
Куда не доглядел? Заранее большой рахмад.
- document.images["webcam"].src = "test.php";
Добавляй хоть какой-нибудь random_id туда, например: ... = "test.php?".Math.rand();
Может поэтому IE и отказывается её грузить. - header("Last-Modified: ".$ftime);
Насколько я помню, в заголовках время никогда не указывается как timestamp, а используется UTC. В PHP для этого кажется gmtime() есть, ну, там в хелпе пример был на эту тему.
А вот разве приписывание параметра-рандома к ссылке не приведет к ее постоянному обновлению, т.е. броузер ее постоянно будет пытать грузить с сервера, даже не думая о том что у него в кэшэ?
Заранее благодарю за ответы.
Так дело в том, что неверный формат Last-Modified может заставить браузер вообще не кешировать картинку. На его усмотрение.
Гм, ну да. Я бы посоветовал сделать так: в JavaScript'е к test.php добавляешь Date.now(); в PHP вместо статуса 304 делаешь header('Location: test.php?'.$ftime);
В добавок к этому можешь поубирать все 4 заголовка, что идут у тебя после if, оставив только один, который заставит браузер кешировать файл навечно (ну, или на час).
Идея вот в чём: браузер каждую секунду запрашивает картинку по её времени изменения. Затем он её кеширует. При запросе, когда картинка не изменилась, скрипт выдаёт редирект на последнюю версию картинки, которая должна быть уже закеширована браузером.
Хотя кеширование может не везде работать, поэтому, чтобы не редиректить браузер бесконечно, проверяешь переданный timestamp (в $_SERVER['QUERTY_STRING'], например), и если он равен $ftime, то выводишь картинку (как у тебя сейчас после if).
подскажи, пожалуйста, а что за JS метод now такой для Date?
Пытался его заменить getTime'мом, так он строку дает длиннее чем filemtime. Только ли дело в том что первый возвращает миллисекунды, а второй секунды?
Пытался его заменить getTime'мом, так он строку дает длиннее чем filemtime. Только ли дело в том что первый возвращает миллисекунды, а второй секунды?
А, now() не стандартный метод. Можно заменить его этим (ref):
Это вернёт timestamp, как раз то, что используют PHP'шные функции, например, filemtime()
ВОбщеМ, подправил я код - FF и ёпера работают, а IE не хочет(иногда кажет alt вместо картинки). Покопавшись в RFC2616, понял что походу виноват хедер location, неужели надо лепить проверку браузера и все-таки давить на IE ETag'ом, if-modified-since'ом и last-modified'ом ?
$filename = ("testcam.jpg");
@clearstatcache();
$fsize=filesize($filename);
$ftime=filemtime($filename);
$time_mod= date("D, d M Y H:i:s",($ftime)) . " GMT";
if ($_SERVER['QUERY_STRING'] == $ftime || ($fsize<2000))
{
header('Location: test.php?'.$_SERVER['QUERY_STRING']);
exit();
}
header("Content-Type: image/jpg");
header( "Cache-Control: public, max-age=1800" );
// header("Last-Modified: ".$time_mod);
readfile($filename);
?>
Функция в JS:
[HTML]<script language="Javascript">
<!--
var refreshtime=1000;
function refreshCam()
{
rfsh = "?"+Math.round( (new Date()).getTime() / 1000 );
document.images["webcam"].src = "test.php"+rfsh;
setTimeout("refreshCam()", refreshtime)
}
//-->
</script>[/HTML]
PS Proger_XP, огромное спасибо тебе за хинты. Не ожидал что такой геммор с браузерами, и что есть люди которые помогают плотно))
- $fsize<2000
Я всё-таки не совсем понимаю, зачем тут проверка на размер. - $_SERVER['QUERY_STRING'] == $ftime
Должно быть $_SERVER['QUERY_STRING'] >= $ftime - иначе ты будешь выдавать неизменившуюся картинку ($ftime), когда клиент будет просить тебя позже даты её изменения (query string). - date("D, d M Y H:i:s",($ftime))
Я же говорил, что время указывается без сдвига временной зоны - для этого нужно использовать gmdate (с теми же параметрами). В хелпе по PHP есть как раз пример на тему различий date и gmdate.
Впрочем, это всё мелкие недочёты, вряд именно они ломают IE. А когда он показывает alt? Есть какая-то закономерность? Пробовал на IE 8?
Кстати, если у тебя одновременно каждую секунду картинка читается и записывается, то может readfile() читает её как раз в момент записи?
Для блокировки можно использовать flock(), правда, в винде он не работает. Ну, можно свой сэмурировать.
И ещё кое-что: кэширование не ограничивается одним заголовком. Например, для отмены кэширования обычно используют четыре, вроде таких:
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); // always modified
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Pragma: no-cache"); // HTTP/1.0
Cache-Control у тебя уже есть, остальные три переверни (last modified = gmdate(..., $ftime) и т.д.).
Всякое бывает :) Кстати, с браузерами гораздо больше мороки (опять же из-за IE) в плане вёрстки и JS, чего в твоём случае не так много.
а это если картинка не догрузилась и меньше "стандартного" размера стала, т.е. чтобы часть картинки не показывалась.
запамятовал, ну вроде и так работает, но впредь учту, спасибо)
gmdate был раьнше, date остался после экспериментов. не понимаю, сервак пишет Date: Fri, 12 Mar 2010 20:12:08 GMT, хотя должно быть на 3 часа позже. пример с финляндией, я уже видел, и что-то он меня не впечатлил))
так разве я тут не пытаюсь боротся, наоборот, за включение кэширования? Да и заголовки эти , ранее, я уже прикручивал...не айс(
Так ты подумай, что у тебя скрипт будет делать - он будет бесконечно редиректить браузер сам на себя. Да и потом, проверку на размер нужно не тут производить наверно, а в клиенте, который закачивает эту картинку. Например, пишет её в один файл (временный), а потом при удачной закачке переписывает её в другой файл (testcam.jpg у тебя).
Тогда частичная загрузка тебе не грозит.
Иначе пропадает весь смысл в кешировании...
Наоборот - если GMT + 3 часа по Москве, то gmdate() тебе пишет всё правильно - на три часа раньше. В заголовках указывается время по Гринвичу, без всяких добавлений времянных зон - иначе как разруливать, когда браузер в одной части земного шара, а сервер - в другой?
Я же написал:
Речь о том, что если для отмены кеширования нужны 4 заголовка, то, логично, для его нормального включения нужны тоже 4 заголовка.
блин, прочитал не пЕреверни, а прИверни)))
логично!:)
изменил, IE все-равно тупит..
$filename = ("testcam.jpg");
@clearstatcache();
$fsize=filesize($filename);
$ftime=filemtime($filename);
$time_mod= gmdate("D, d M Y H:i:s",($ftime)) . " GMT";
if ($_SERVER['QUERY_STRING'] >= $ftime || ($fsize<5000))
{
header('Location: test.php?'.$_SERVER['QUERY_STRING']);
exit();
}
header("Content-Type: image/jpg");
header( "Cache-Control: public, max-age=1800" );
header("Expires:".gmdate("D, d M Y H:i:s",($ftime+1800)) . " GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s",$ftime)." GMT");
header("Pragma: cache");
readfile($filename);
?>
Кстати, проверка размера файла мне кажется удачным решением. Ведь ты же сам говорил что редирект идет на кэш, а потому картинка будет постоянно на экране(должна быть), а если сделать ее обновление ~0,1сек, то я с большой долей вероятности избегаю попадания ридфайла во время загрузки картинки на сервер. По крайней мере, выглядит в ФФ и ёпере все неплохо. А IE вообще плюет на кэш, и выводит альт через раз загрузки jpeg'a.
кстати, вот резуль сервера на скрипт:
Server: nginx/0.4.13
Date: Sat, 13 Mar 2010 07:58:30 GMT
Content-Type: image/jpg
Cache-Control: public, max-age=1800
Expires: Sat, 13 Mar 2010 08:28:29 GMT
Pragma: cache
X-Powered-By: PHP/4.4.4-8+etch6
Last-Modified: Sat, 13 Mar 2010 07:58:29 GMT
200 OK
Видимо по 200 коду он и не думает в кэш глядеть, онж ему постоянно приходитО_о
Если появтся идеи, буду рад, а пока буду шаманить далее. Спасибо!
Это бывает :D
Честно говоря, не удивляет...
С кодом вроде всё ок, правда один мометнт - не понятно, зачем тебе $time_mod: во-первых, неиспользуешь, во-вторых, я бы не стал её вообще определять, раз уж один раз может использоваться (подразумевается Last-Modified). Ну это дело вкуса.
Так ты подумай, какой тут кеш - если б браузер имел кеш на эту конкретную страницу, то он бы его и использовал, а не запрашивал бы сервер. А так ты его редиректишь на ту же самую страницу, которую он и запросил.
Я бы использовал любой из двух подходов, которые уже описывал (flock() или использование одного временного файла и подмена им настоящего по завершению загрузки).
Ну или, если уж делать по-твоему, то по крайней мере имеет смысл поменять учасок с условием так:
{
header('Location: test.php?'.($ftime - 1));
exit();
}
Здесь браузер редиректится на реально последнее время изменение файла (а не QS, которая может быть хоть 100 часов в будущем), и минус 1 секунда, чтобы браузер мог использовать кеш, который он запомнил при запросе предыдущего кадра.
Да нет, по идее (по стандарту) всё правильно - код 200 как раз нормальный показатель, что страницу можно кешировать. Впрочем, кто его знает, что в голове у UE...
Так ты пробовал это в 8 версии оного?
Хм, ну честно говоря эта идея (с кешированием) выглядит наиболее простой. Правда, если уж так глухо с IE, то можно повозиться над клиентской частью - JS то есть. Например, ты можешь сделать интерфейс вроде такого:
- test.php?action=isnewframe&since=timestamp - скрипт должен вернуть либо только 0 - кадр старый, либо 1 - появился новый кадр. timestamp - последний кадр, который JS запрашивал (и запомнил, соответственно).
- test.php?action=lastframe - сервер всегда возвращает само содержимое кадра (картинку). Грубо говоря, можно даже не скрипт запрашивать, а сразу статическую картинку (посколько скрипт будет всегда редиректить на неё, если не придумаешь каких-то дополнительных проверок/действий).
Поэтому как бы криво браузер не располряжался кешированием тут ему ничего не сделать, так как этим занимается сам скрипт.
Как запрашивать сервер ты уж точно найдёшь кроссбраузерный способ - например, iframe или XMLHttpRequest.
При таком подходе, конечно, нагрузка на сервер часто (зависит от частоты обновления кадра) будет в 2 раза выше, чем при подходе, который мы пытались сделать до сих пор.
Но по крайней мере это должно работать даже в IE.
Proger_XP, огромное спасибо за уделенное время.
Proger_XP, огромное спасибо за уделенное время.
Без проблем :)