|
|
|
| Если закачивать файл с возможностью докачки, можно ли как-то управлять закачкой отдельных фрагментов?
Например, если пользователь скачал 1 и 4 фрагмент и неожиданно отключился, может ли сервер зафиксировать то, что 1 и 4 фрагменты уже есть, значит, при повторном подключении 1 и 4 фрагменты уже не давать, а только оставшиеся 2 и 3? И где тогда это фиксируется?
Была мысль сохранить 1 и 4 фрагмент в кэше, но в RFC пишется, что кэш не поддерживает заголовки Range и Content-Range, отклики 206 (Partial) не кэшируются. Как тогда быть?
И есть ли способ сообщить пользователю о том, сколько ему осталось закачать? Например, что-то типа такого: "Вы уже скачали 25% книги, чтобы скачать остальное, вам понадобится 2 минуты". | |
|
|
|
|
|
|
|
для: Лена
(22.02.2009 в 20:33)
| | Вообще-то при частичной докачке клиент сам запрашивает (Range: bytes... ), какой фрагмент ему нужен.
Клиент же считает прогноз по времени.
Серверу остается лишь отдать (206 Partial content и Content-range:...) запрошенный фрагмент. | |
|
|
|
|
|
|
|
для: Trianon
(22.02.2009 в 20:37)
| | Весь текст делится на равные фрагменты, например 0-100, 100-200 и т.д. В Content-Range: bytes и будут отмечены эти фрагменты(+ полный размер файла). А если какой-то фрагмент оборвется посредине? Как это сообщается серверу?
>Клиент же считает прогноз по времени.
Как посчитать, сколько времени осталось, чтобы закачать весь фрагмент? | |
|
|
|
|
|
|
|
для: Лена
(22.02.2009 в 20:52)
| | нет. не так.
1. сервер сообщает клиенту полем Accept-Ranges: bytes что он в принципе непротив обслуживать диапазонные запросы.
2. Клиент выдает запрос с полем Range: bytes; nnnnn-
где nnnnn - позиция , с которой начинается недокачаный участок.
Выбор участка целиком и полностью право и обязанность клиента. Как поделит - так и захочет. как захочет - так и попросит. Как попросит - так сервер должен будет выполнить.
Синтаксис на самом деле более сложный , но приведенная форма самая переносимая. Означает она, что фрагмент берется до конца файла, и случчего клиент прервет соединение, если нарвется на уже закачанный участок.
3. сервер отвечает
HTTP/n.n 206 Partial content
Content-Length: lllll
Content-Range: bytes nnnnn-mmmmm/sssss
Content-Type: application/octet-stream
фрагмент
где lllll - длина фрагмента
mmmmm - позиция конца фрагмента - 1
sssss - полный размер файла
mmmmm при верхнем запросе , понятное дело, равна sssss-1 | |
|
|
|
|
|
|
|
для: Trianon
(22.02.2009 в 21:06)
| | >где nnnnn - позиция , с которой начинается недокачаный участок
не пойму, откуда клиент узнает эту позицию? Как он может ее узнать, если процессом закачки управляет сервер?
>случчего клиент прервет соединение, если нарвется на уже закачанный участок.
а может быть так, что этот закачанный участок будет находиться до позиции nnnnn, которую определил клиент? | |
|
|
|
|
|
|
|
для: Лена
(22.02.2009 в 21:52)
| | >>где nnnnn - позиция , с которой начинается недокачаный участок
>не пойму, откуда клиент узнает эту позицию? Как он может ее узнать, если процессом закачки управляет сервер?
процессом закачки управляет клиент.
>>случчего клиент прервет соединение, если нарвется на уже закачанный участок.
>а может быть так, что этот закачанный участок будет находиться до позиции nnnnn, которую определил клиент?
Вполне.
Типичный случай закачки длинного файла каким нибудь многопоточным менеджером закачки, вроде flashget.
К примеру файл 1000000 байт длиной и в настройках менеджера указано тащить в 5 потоков.
Менеджер устанавливает пять соединений, указывая в их get-запросах такие поля:
Range: bytes; 0-
Range: bytes; 200000-
Range: bytes; 400000-
Range: bytes; 600000-
Range: bytes; 800000-
Серверу ничего не остается как ответить вот такими кусками.
200 OK (поскольку это весь файл)
Content-Length: 1000000
206 Partial content,
Content-Length: 800000
Content-range: bytes; 200000-999999/1000000
206 Partial content,
Content-Length: 600000
Content-range: bytes; 400000-999999/1000000
206 Partial content,
Content-Length: 400000
Content-range: bytes; 600000-999999/1000000
206 Partial content,
Content-Length: 200000
Content-range: bytes; 800000-999999/1000000
В графике загрузки прекрасно видно что происходит дальше.
Ползут пять удавчиков.
Как только любой из удавчиков наползает на уже скачанное место, он помирает, а вместо него образуется другой на середине самой длинной дыры. | |
|
|
|
|
 3.4 Кб |
|
|
для: Trianon
(22.02.2009 в 22:51)
| | Вот что получилось (см. аттач), там с комментариями, должно быть понятно.
Старалась делать пошагово, как вы описали.
Но нажимаю на ссылку - выскакивает окно:
"IE не удается загрузить ... из ...
Не удается открыть этот узел Интернета. Узел недоступен или не найден...." | |
|
|
|
|
|
|
|
для: Лена
(23.02.2009 в 17:23)
| | Ошибка в определении $to в строке 52 и 58 . Не все форматы range учитываются.
Ошибка в строке 74 . Вы уменьшаете переменную для одной строки, а назад её не корректируете.
А потом пользуетесь на 85-й. С ошибкой.
Ошибка в fread в 90 строке - теряется считанный фрагмент. | |
|
|
|
|
|
|
|
для: Trianon
(23.02.2009 в 20:44)
| | >Ошибка в определении $to в строке 52 и 58 . Не все форматы range учитываются.
Я написала, если диапазон задан и если значения диапазона не заданы.
Может, вот это еще нужно было учесть? Если конечный элемент не задан, считываем до конца файла: if(!$to) $to = $size;
>Ошибка в fread в 90 строке - теряется считанный фрагмент.
Получается, все считанное надо писать в переменную. А если фрагменты будут перекрывать друг друга? Например, один кусок будет 200-399, второй 200-599(это если оборвалась связь). | |
|
|
|
|
|
|
|
для: Лена
(23.02.2009 в 23:14)
| | есть три формата задания диапазона
aaaa-bbbb : все байты с aaaa и по bbbb
aaaa- : все байты с aaaa и до конца файла
-ssss : это не от начала и по ssss, как многие (в том числе и я когда-то) заблуждаются.
это означает последние ssss байт файла. Хвостовой фрагмент.
>Получается, все считанное надо писать в переменную.
Почему в переменную? echo надо делать.
>А если фрагменты будут перекрывать друг друга?
Например, один кусок будет 200-399, второй 200-599(это если оборвалась связь).
Если это два разных запроса - совершенно не должно трогать. Клиент сам разберется, откуда что и куда.
Если два кустка в одном ... в принципе в HTTP/1.1 есть мультидиапазонные запросы, но Вы их, по-моему, реализовывать не собираетесь. Вообще, они больше для транспортных уровней характерны, а не для прикладных. | |
|
|
|
|
|
|
|
для: Trianon
(23.02.2009 в 23:24)
| | >-ssss : это не от начала и по ssss, как многие (в том числе и я когда-то) заблуждаются.
Не пойму. Поэтому и не могу необходимое условие поставить.
Если пользователь запросил диапазон такой:
-499
Согласно моему коду, $from здесь не определено, $to = 499. Почему это вы назвали хвостовым фрагментом, если нужно считать с какого-то байта($from ) до 499, а если в файле 800 байт, это уже будет не хвостовой фрагмент.
И еще одно. А если $from = 499, получается, что вообще ничего считано не будет? | |
|
|
|
|
|
|
|
для: Лена
(24.02.2009 в 23:35)
| | >>-ssss : это не от начала и по ssss, как многие (в том числе и я когда-то) заблуждаются.
>
>Не пойму. Поэтому и не могу необходимое условие поставить.
>Если пользователь запросил диапазон такой:
>-499
>Согласно моему коду, $from здесь не определено, $to = 499. Почему это вы назвали хвостовым фрагментом, если нужно считать с какого-то байта($from ) до 499, а если в файле 800 байт, это уже будет не хвостовой фрагмент.
потому что -499 означает вовсе не то, что from неопределен, а to равен 499.
-499 означает что сервер должен отдать 499 последних байт.
Лена, Вы RFC-2616 глядели (хотя бы в переводе) или только по подсказкам алгоритм строите?
suffix-byte-range-spec = "-" suffix-length
suffix-length = 1*DIGIT
Спецификация suffix-byte-range-spec используется для задания суффикса тела объекта с длиной, заданной значением suffix-length. (То есть, эта форма специфицирует последние N байтов тела объекта.) Если объект короче заданной длины суффикса, то в качестве суффикса используется все тело объекта. | |
|
|
|
|
|
|
|
для: Trianon
(24.02.2009 в 23:47)
| | >Лена, Вы RFC-2616 глядели
Читала выборочно и быстро. А надо было подряд.
Ушла еще раз читать. Когда осилю, составлю код. Все равно я его добью.
Спасибо за помощь. | |
|
|
|
|
|
|
|
для: Лена
(25.02.2009 в 00:01)
| | Пожалуй, я оставлю здесь ссылки на главы перевода RFC-2616, связанные с темой, и номера параграфов оригинального документа.
9.2.7. 206 Partial Content (Частичное содержимое)
--- 10.2.7 206 Partial Content
(не описан в переводе)
--- 10.4.17 416 Requested Range Not Satisfiable
12.10.4 Комбинирование байтовых фрагментов
--- 13.5.4 Combining Byte Ranges
13.5. Поле Accept-Ranges
--- 14.5 Accept-Ranges
13.17. Отрывок содержимого
--- 14.16 Content-Range
13.27. Заголовок If-Range
--- 14.27 If-Range
13.36. Фрагмент (обе главы)
14.35 Range
16.2. Тип среды Интернет "multipart/byteranges"
--- 19.2 Internet Media Type multipart/byteranges | |
|
|
|
|
|
|
|
для: Trianon
(25.02.2009 в 00:08)
| | Спасибо.
Самое интересное, что кроме вас никто по докачке файлов толкового кода не сделал.
Сложная задача. | |
|
|
|
|
 8 Кб |
|
|
для: Лена
(25.02.2009 в 00:22)
| | Сделала. В аттаче.
Скрипт работает.
Если что не правильно записано, говорите. | |
|
|
|
|
|
|
|
для: Лена
(26.02.2009 в 14:33)
| | Мне трудно оценить. У меня выползет куча неопределенных переменных.
case $ext[$i]: где $ext[$i] - массив , я не понял...
С вызовом fpassthru у Вас какая-то ерунда.
Не понял , зачем нужно после вывода хвостового фрагмента делать вывод его длины.
И с IF_RANGE тоже странно как-то. То ли он у Вас md5-метка, то ли время модификации...
Отработка 304 Not modified какая-то куцая.
что за заголовки после него выводятся?
И как заблокирован вывод тела файла?
Аналогично с 412. | |
|
|
|
|
|
|
|
для: Trianon
(26.02.2009 в 14:57)
| | >У меня выползет куча неопределенных переменных.
У меня все данные про файл хранятся в базе (имя, путь, расширение и т.д.) Потом в скрипте я эти данные использую. Вот почему у вас неопределенные переменные
>case $ext[$i]: где $ext[$i] - массив , я не понял...
Это не учла, забыла. Уже исправила, спасибо.
>С вызовом fpassthru у Вас какая-то ерунда.
Правда ерунда. Думала, что fpassthru по-другому работает. На примере проверила, поняла.
>И с IF_RANGE тоже странно как-то.
Формат заголовка такой:
If-Range = "If-Range" ":" (entity-tag | HTTP-date)
Заголовок может содержать или метку, или время последней модификации. Я учла оба способа. Ведь по первым двум символам сервер может отличить дату от метки.
Записала условием: if($_SERVER['HTTP_IF_RANGE'] == $etag || strtotime($_SERVER['HTTP_IF_RANGE']) >= $last_mod || !$_SERVER['HTTP_IF_RANGE']){
Что здесь неправильного?
>И как заблокирован вывод тела файла?
Это о чем? Не поняла.
Про 304 и 412 еще почитаю и подумаю, как сделать. | |
|
|
|
|
|
|
|
для: Лена
(26.02.2009 в 16:14)
| | >>У меня выползет куча неопределенных переменных.
>У меня все данные про файл хранятся в базе (имя, путь, расширение и т.д.) Потом в скрипте я эти данные использую. Вот почему у вас неопределенные переменные
Да нет, не поэтому.
К примеру if($_SERVER['HTTP_IF_RANGE'] == $etag сразу же выдаст нотайс, если поля IF_RANGE не окажется в заголовке запроса. И т.п.
>>И с IF_RANGE тоже странно как-то.
>Формат заголовка такой:
>If-Range = "If-Range" ":" (entity-tag | HTTP-date)
>Заголовок может содержать или метку, или время последней модификации. Я учла оба способа. Ведь по первым двум символам сервер может отличить дату от метки.
Может :) Но не святым же духом. Это задача программиста, то есть Ваша. :)
>Записала условием: if($_SERVER['HTTP_IF_RANGE'] == $etag || strtotime($_SERVER['HTTP_IF_RANGE']) >= $last_mod || !$_SERVER['HTTP_IF_RANGE']){
>Что здесь неправильного?
Условный блок должен начинаться с if(isset($_SERVER['HTTP_IF_RANGE']
Что до самой логики, то
а) хеш считается не от имени файла, а от его содержимого. И лучше бы он был посчитан один раз - это довольно ресурсоемкая операция.
б) дата модификации должна указываться и восприниматься по UTC (gmdate)
в) if(!$etag && -- etag у Вас никогда нулем не будет - смысла продолжения строки я не понял.
г) if(strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) <= $last_mod при отсутствии поля
IF_MODIFIED_SINCE приведет к тому, что будет выдан 304 Not modified
... ну и так далее...
>>И как заблокирован вывод тела файла?
>Это о чем? Не поняла.
Вы выдаете header 304 Not Modified
потом
header('Content-Type: '. $mime);
header('Content-Disposition: attachment; filename=' . basename($filename));
header("Last-Modified: " . gmdate("D M Y H:i:s T",$last_mod));
потом что?
Потом идет нотайс по неопределенному $from, $to
пара полей
header("HTTP/1.1 200 OK");
header('Content-Length:' . $size);
и цикл вывода файла.
При том что тело выдаваться не должно.
кстати, следующая строка
//узнаем, запросил клиент полный файл либо кусок
if ($from > 0 || $to < $size) {
> | |
|
|
|
|
 9.7 Кб |
|
|
для: Trianon
(26.02.2009 в 18:45)
| | Новый вариант. В аттаче.
А вот заблокировать не получилось. | |
|
|
|
|
|
|
|
для: Лена
(28.02.2009 в 20:25)
| |
<?php
include_once("configs/dbopen.php");
//получаем нужную книгу по ссылке
if(isset($_GET['id_book'])){
//выбираем название книги, путь и расширение файла из базы
$sql = "SELECT * FROM books WHERE id =" . $_GET['id_book'];
$res = mysql_query($sql);
if(!$res) exit ("Error in $sql:" . mysql_error());
while($row = mysql_fetch_assoc($res)){
//print_r($row);
//определяем расширение
$extension = $row['type'];
$ext = array('zip', 'rar',
array('gz', 'tgz'),
array('jpg','jpe','jpeg'), 'gif', 'png', 'txt', 'pdf', );
$stream = array('application/x-zip', 'application/x-rar-compressed',
'application/x-gzip', 'image/jpeg', 'image/gif', 'image/png',
'text/plain', 'application/pdf');
for($i=0;$i<count($ext);$i++){
switch($extension){
case $ext[$i]:
if(is_array($ext[$i])){
foreach($ext[$i] as $key => $ei)
$mime = $stream[$i][$key];
}else
$mime = $stream[$i];
break;
default:
$mime = 'application/octet-stream';
break;
}
}
$filename = $row['path'] . "/". $row['title'] . "." . $row['type'];
//проверка на существование и воспроизведение нужного файла
if (!file_exists($filename) && !is_readable($filename))
exit('File does not exist or don`t readable');
//сервер сообщает клиенту, что непротив обслуживать байт-диапазонные запросы
header('Accept-Ranges: bytes');
$f = fopen($filename, 'rb');
//проверка, открылся ли файл
if(!$f){
header ("HTTP/1.0 505 Internal server error");
exit();
}
flock($f, LOCK_EX);
fseek($f, 0, SEEK_END); //указатель - в конец файла, получаем позицию указателя
$size = ftell($f);
//метка объекта, хеш всего файла
$etag_server = md5_file($filename);
//время последней модификации файла
$last_mod = filemtime($filename);
//клиент запросил докачку
if (isset($_SERVER['HTTP_RANGE'])) {
//проверяем, что лежит в $_SERVER['HTTP_RANGE']: bytes=20-50/длина файла
if (!preg_match('/^bytes=(?:\d+-\d+|-\d+|\d+-)(?:,(?:\d+-\d+|-\d+|\d+-))*//\d+$/',
$_SERVER['HTTP_RANGE']))
return false;
// условные запросы
if(isset($_SERVER['HTTP_IF_RANGE']) && $_SERVER['HTTP_IF_RANGE'] == $etag_server
|| strtotime($_SERVER['HTTP_IF_RANGE']) >= $last_mod)
{
//определяем, чем являются первые символы - меткой или датой
if(!preg_match('/^\d{2}\s\d{2}\s\d{4}\s\d{2}:\d{2}:\d{2}\sGMT/',
$_SERVER['HTTP_IF_RANGE']))
{
//если это метка, проверяем, совпадает ли метка объекта с текущей
if(isset($_SERVER['HTTP_IF_MATCH']) && $_SERVER['HTTP_IF_MATCH'] != "*"){
$etag_client = $_SERVER['HTTP_IF_MATCH'];
//метка не совпала
if($etag_client !== $etag_server){
//надо присвоить новую метку
header("ETag: \"". $etag_server ."\"");
// зафиксировать последнее изменение (сейчас)
header ("Last-Modified: " . gmdate("d M Y H:i:s \G\M\T", time()));
//страница не будет меняться 10 минут
header("Expires: " . gmdate("d M Y H:i:s \G\M\T", time()+60*10));
}
}else{
header("HTTP/1.1 412 Precondition Failed");
exit();
}
}else{
//если это дата, проверяем время модификации
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
&& strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_mod)
{
header ("Date: " . gmdate ("D, d M Y H:i:s \G\M\T"));
header("HTTP/1.1 304 Not Modified"); //файл не поменялся
}else{
if(strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) > $last_mod){
//файл изменился, присваиваем новую метку
header("ETag: \"". $etag_server ."\"");
// последнее изменение - сейчас
header ("Last-Modified: " . gmdate("d M Y H:i:s \G\M\T", time()));
//страница не будет меняться 10 минут
header("Expires: " . gmdate("d M Y H:i:s \G\M\T", time()+60*10));
}
}
}
//определяем диапазон
$range=substr($_SERVER['HTTP_RANGE'], strpos($_SERVER['HTTP_RANGE'], '=')+1);
$range = substr($range, strpos($range,0,"/"));
$range = explode("-", $range);
$from = $range[0];
$to = $range[1];
//если первая позиция не задана, берем ее с нуля
if($from < 0) $from = 0;
if($to) $to = ++ $to;
//если не задана позиция конца
else $to = $size;
//первые байты, диапазон типа 521000-
if($range[1]==''){
$from = 0;
$to = $range[0];
}
//последние байты, диапазон типа -300000
if($range[0]==''){
$from = $range[1];
$to = $size;
}
//узнаем, запросил клиент полный файл либо кусок
if ($from > 0 || $to < $size) {
//первый и последний байт куска
$start = $from;
$end = $to - 1;
header('HTTP/1.1 206 Partial Content');
//метка
header("ETag: \"". $etag_client ."\"");
//длина диапазона, который считывается
$part = $to - $from;
header('Content-Length:' . $part);
header('Content-Range: bytes ' . $start . "'-'" . $end . "'/'" . $size);
}else{
// если клиент не запросил докачку или byte-range-spec некорректна
header("HTTP/1.1 200 OK");
header('Content-Length:' . $size);
}
}else
header("HTTP/1.1 416 Requested Range Not Satisfiable");
}else{
//клиент запросил весь файл
$range = array(0,$size);
$from = $range[0];
$to = $range[1];
$start = $from;
}
//отправляем заголовки и для целого файла, и для частичной загрузки
header('Content-Type: '. $mime);
header('Content-Disposition: attachment; filename=' . basename($filename));
//ставим указатель на начальную позицию, которую задал клиент
fseek($f, $start, SEEK_SET);
//выдача файла
while(!feof($f)) {
if($to == $size){
//выводим остаток до конца файла
fpassthru($f);
}else{
//выводим начало и середину
$read = fread($f, 256000);
echo $read;
flush();
}
}
flock($f, LOCK_UN);
fclose($f);
}//while($row =
//счетчик скачиваний
$sql_update = "UPDATE books SET count_download = count_download + 1 WHERE id ="
. $_GET['id_book'];
mysql_query($sql_update);
}//if(isset($_GET
?>
|
а теперь можно будет и посмотреть... | |
|
|
|
|
|
|
|
для: Trianon
(01.03.2009 в 11:33)
| | 1. блок определения mime работает как-то странно.
У меня он возвращает такой результат:
zip => application/octet-stream
rar => application/octet-stream
gz => application/octet-stream
tgz => application/octet-stream
jpg => application/octet-stream
jpe => application/octet-stream
jpeg => application/octet-stream
gif => application/octet-stream
png => application/octet-stream
txt => application/octet-stream
pdf => application/pdf
|
это вполне работоспособно, но ... ей-богу , достигается куда меньшими усилиями. | |
|
|
|
|
|
|
|
для: Trianon
(01.03.2009 в 11:33)
| | 2. Вычисление хеша файла.
flock($f, LOCK_EX);
fseek($f, 0, SEEK_END); //указатель - в конец файла, получаем позицию указателя
$size = ftell($f);
//метка объекта, хеш всего файла
$etag_server = md5_file($filename);
|
Мы сперва блокируем файл, а потом пытаемся запросить вычисление хеша.
и как функция md5_file станет работать с заблокированным файлом?
Кроме того, как я уже сказал, операция md5_file() довольно ресурсооемкая. Лучше бы хранить посчитанное значение хеша прямо в таблице. | |
|
|
|
|
|
|
|
для: Trianon
(01.03.2009 в 11:33)
| | 3. проверка формата Range:
if (!preg_match('/^bytes=(?:\d+-\d+|-\d+|\d+-)(?:,(?:\d+-\d+|-\d+|\d+-))*//\d+$/',
$_SERVER['HTTP_RANGE']))
return false;
|
заканчивается каким-то return, хотя по идее достаточно всего лишь считать поле Range отсутствующим, и выдать полный контент.
Кроме того, шаблон выглядит ... странно.
В нем явно вырисовываются следы возможности обработки мультидиапазонных запросов, в то время как сам скрипт явно к этому не готов.
При чем именно следы. Синтаксис явно нарушен...
Такое впечатление, что РБНФ прямо воткнута в конец шаблона. | |
|
|
|
|
|
|
|
для: Trianon
(01.03.2009 в 11:33)
| | 4.
// условные запросы
if(isset($_SERVER['HTTP_IF_RANGE']) && $_SERVER['HTTP_IF_RANGE'] == $etag_server
|| strtotime($_SERVER['HTTP_IF_RANGE']) >= $last_mod)
|
в этом фрагменте strtotime() будет работать снеопределенным элементом HTTP_IF_RANGE массива $_SERVER
что-то тут не так. | |
|
|
|
|
|
|
|
для: Trianon
(01.03.2009 в 11:33)
| | 5.
//определяем, чем являются первые символы - меткой или датой
if(!preg_match('/^\d{2}\s\d{2}\s\d{4}\s\d{2}:\d{2}:\d{2}\sGMT/',
$_SERVER['HTTP_IF_RANGE']))
|
Слова стандарта "Сервер может отличить корректную дату HTTP от любой формы метки объекта, рассмотрев не более двух символов" - это не повод проверять всю дату на соответствие,
тем более что как клиентская, так и серверная часть должна быть достаточно толлерантна к форматам даты.
на самом деле, поскольку в первых двух буквах названия дня недели встречается хотя бы один из нешестнадцатеричных символов, достаточно написать что-то вроде
$ifr = trim($_SERVER['HTTP_IF_RANGE']);
$iftag = $ifdate = '';
if(ctype_digit($ifr)) $iftag = $ifr; else $ifdate = strtoftime($ifr);
|
| |
|
|
|
|
|
|
|
для: Trianon
(01.03.2009 в 11:33)
| | 6.
header("ETag: \"". $etag_server ."\"");
// зафиксировать последнее изменение (сейчас)
header ("Last-Modified: " . gmdate("d M Y H:i:s \G\M\T", time()));
//страница не будет меняться 10 минут
header("Expires: " . gmdate("d M Y H:i:s \G\M\T", time()+60*10));
|
Все эти параметры должны выдаваться в любом заголовке отклика. А не только под условиями.
Кстати, почему в качестве last-modified выдается текущее время, если файл имеет явное время модификации?
Опять же
header("HTTP/1.1 412 Precondition Failed");
exit();
|
перед exit еще по идее должны следовать Content-type и прочие характеристики объекта. | |
|
|
|
|
|
|
|
для: Trianon
(01.03.2009 в 11:33)
| | 7
//первые байты, диапазон типа 521000-
if($range[1]==''){
$from = 0;
$to = $range[0];
}
|
это как? | |
|
|
|
|
|
|
|
для: Trianon
(01.03.2009 в 12:34)
| | 1. Этот блок не так должен работать. Как должен, сама сделаю и покажу.
Получается, если скрипт большой, надо куски отдельно проверять. А здесь я не правильно проверила. Просто загружала файлы с разными расширениями, а надо было не так делать.
2.
>Лучше бы хранить посчитанное значение хеша прямо в таблице.
В таблице базы? Но если файл большой и таких файлов много? Это ж как базу раздует...
3. С этим согласна.
4. Подумаю и переделаю.
5. Вот до этого не додумалась :)
6.
>Кстати, почему в качестве last-modified выдается текущее время, если файл имеет явное время модификации?
Потому что файл изменился и нужно зафиксировать время, когда он изменился. Изменился сейчас - ставим время на данный момент.
7.
>это как?
Если диапазон такой: 521000- , мы должны взять первые байты тела объекта, тогда начинаем брать с нулевого байта и до 521000, значит $from = 0; $to = $range[0]; - или в этом случае $to = 521000.
if($range[1]==''){ - если элемент массива(который мы получали explode), пустой, то...
Аналогичным образом определяла диапазон типа -9000, когда нужно взять последние байты тела объекта.
Остальное буду исправлять. | |
|
|
|
|
|
|
|
для: Лена
(01.03.2009 в 17:00)
| | 2. 32 байта на запись? Мелочь.
Как вариант можно сами файлы держать под именами, равными md5. Или md5 с seq-суффиксом.
6. но изменился-то он именно тогда, когда filemtime, а не позже?
7.
>Если диапазон такой: 521000- , мы должны взять первые байты тела объекта,
нет. см. (22.02.2009 в 22:51)
521000- означает "Взять с позиции 521000 и до конца файла".
И это, кстати 99.9% всех издаваемых диапазонных запросов.
>Аналогичным образом определяла диапазон типа -9000, когда нужно взять последние байты тела объекта.
а здесь верно.
-9000 означает взять последние 9000 байт файла. | |
|
|
|
|
|
|
|
для: Trianon
(01.03.2009 в 17:11)
| | >Получается, если скрипт большой, надо куски отдельно проверять
Безусловно. неплохо его еще и на функции делить.
Я заметил ошибку по тексту, и не поверив в результат, решил этот фрагмент проверить. | |
|
|
|
|
|
|
|
для: Trianon
(01.03.2009 в 17:11)
| | опять же, учтите , что
------------------------------------------------------
13.29. Поле Last-Modified
Поле заголовка объекта Last-Modified указывает на дату и время, при которых, по мнению исходного сервера, данный объект был модифицирован.
Last-Modified = "Last-Modified" ":" HTTP-date
Пример его использования
Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT
Точное значение этого заголовка зависит от реализации исходного сервера и природы ресурса. Для файлов, это может быть дата последней модификации файловой системы. Для объектов с динамическими встроенными частями это может время последней модификации одной из встроенных компонент. Для шлюзов баз данных это может быть метка последней модификации рекорда. Для виртуальных объектов это может быть время последнего изменения внутреннего состояния.
Исходный сервер не должен посылать дату Last-Modified, которая позже, чем время формирования сообщения сервера. В таких случаях, когда последняя модификация объекта указывает некоторое на время в будущем, сервер должен заменить дату на время формирования сообщения.
Исходный сервер должен получить значение Last-Modified объекта как можно ближе по времени к моменту генерации значения Date отклика. Это позволяет получателю выполнить точную оценку времени модификации объекта, в особенности, если объект был изменен буквально накануне формирования отклика.
Серверы HTTP/1.1 должны посылать поле Last-Modified всякий раз, когда это возможно.
------------------------------------------------------ | |
|
|
|
|
 12.1 Кб |
|
|
для: Trianon
(01.03.2009 в 17:45)
| | > неплохо его еще и на функции делить.
По этому поводу около недели назад был разговор с sim`ом (не на форуме, в привате). В двух словах смысл разговора сводился к тому, что функция - это подпрограмма, после выполнения которой нужно вернуться к основной программе (по адресу, который заносится в стек). Получается такой вот "скачок" во внутренней памяти, поэтому если можно его избежать, функции лучше не использовать. В этом скрипте функции необязательны.
А теперь код. У меня в файле все красиво, все отступы, а здесь искажаются. Пробовала сделать, как надо. На всякий случай нормальный вариант прикрепила.
<?php
include_once("configs/dbopen.php");
//получаем нужную книгу по ссылке
if(isset($_GET['id_book'])){
//выбираем название книги, путь и расширение файла из базы
$sql = "SELECT * FROM books WHERE id =" . $_GET['id_book'];
$res = mysql_query($sql);
if(!$res) exit ("Error in $sql:" . mysql_error());
while($row = mysql_fetch_assoc($res)){
//print_r($row);
//определяем расширение
$extension = $row['type'];
$ext = array('zip', 'rar', 'gz', 'tgz', 'jpg','jpe','jpeg', 'gif', 'png', 'txt', 'pdf', );
$stream = array('application/x-zip', 'application/x-rar-compressed', 'application/x-gzip','application/x-gzip','image/jpeg','image/jpeg','image/jpeg','image/gif', 'image/png', 'text/plain', 'application/pdf');
$arr = array_combine($ext,$stream);
foreach($arr as $k=>$v){
if($extension == $k) $mime = $v;
else $mime = 'application/octet-stream';
}
$filename = $row['path'] . "/". $row['title'] . "." . $row['type'];
//проверка на существование и воспроизведение нужного файла
if (!file_exists($filename) && !is_readable($filename)) exit('File does not exist or don`t readable');
//сервер сообщает клиенту, что непротив обслуживать байт-диапазонные запросы
header('Accept-Ranges: bytes');
$f = fopen($filename, 'rb');
//проверка, открылся ли файл
if(!$f){
header ("HTTP/1.1 505 Internal server error");
exit();
}
flock($f, LOCK_EX);
fseek($f, 0, SEEK_END); //указатель - в конец файла, получаем позицию указателя
$size = ftell($f);
//метка объекта, хеш всего файла, md5_file($filename)
//выбираем из базы, вычисляем при загрузке данных про файл в базу
$etag_server = $row['hash'];
//время последней модификации файла
$last_mod = filemtime($filename);
//клиент запросил докачку
if (isset($_SERVER['HTTP_RANGE'])) {
//проверяем, что лежит в $_SERVER['HTTP_RANGE']: bytes=20-50/длина файла
if (!preg_match('/^bytes=(?:\d+-\d+|-\d+|\d+-//\d+)$/', $_SERVER['HTTP_RANGE'])){
//если range некорректный, посылаем весь текст
header("HTTP/1.1 416 Requested Range Not Satisfiable");
header("HTTP/1.1 200 OK");
header('Content-Type: '. $mime);
header('Content-Disposition: attachment; filename=' . basename($filename));
header('Content-Length:' . $size);
header ("Last-Modified: " . gmdate("D, d M Y H:i:s \G\M\T", $last_mod));
header("Expires: " . gmdate("D, d M Y H:i:s \G\M\T", time()+60*10));
}
// условные запросы
if(isset($_SERVER['HTTP_IF_RANGE'])){
//определяем, чем являются первые символы - меткой или датой
$ifr = trim($_SERVER['HTTP_IF_RANGE']);
$iftag = $ifdate = '';
if(ctype_digit($ifr)){
$ifdate = strtoftime($ifr);
//если это дата, проверяем время модификации
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])){
$ifmod = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
if($ifdate == $ifmod && $ifmod == $last_mod){
header("HTTP/1.1 304 Not Modified"); //файл не поменялся
header("ETag: \"". $etag_server ."\"");
// последнее изменение
header ("Last-Modified: " . gmdate("D, d M Y H:i:s \G\M\T", $last_mod));
//страница не будет меняться 10 минут
header("Expires: " . gmdate("D, d M Y H:i:s \G\M\T", time()+60*10));
exit();
}else{
if($ifmod > $last_mod){
//файл изменился, присваиваем метку
header("ETag: \"". $etag_server ."\"");
// последнее изменение
header ("Last-Modified: " . gmdate("D, d M Y H:i:s \G\M\T", $last_mod));
//страница не будет меняться 10 минут
header("Expires: " . gmdate("D, d M Y H:i:s \G\M\T", time()+60*10));
}
}
}
}else{
$iftag = $ifr;
//если это метка, проверяем, совпадает ли метка объекта с текущей
if(isset($_SERVER['HTTP_IF_MATCH']) && $_SERVER['HTTP_IF_MATCH'] != "*"){
$etag_client = trim($_SERVER['HTTP_IF_MATCH']);
//метка не совпала
if($iftag == $etag_client && $etag_client !== $etag_server){
//присылаем полный объект
header("HTTP/1.1 200 OK");
header('Content-Length:' . $size);
//надо присвоить новую метку
header("ETag: \"". $etag_server ."\"");
// зафиксировать последнее изменение
header ("Last-Modified: " . gmdate("D, d M Y H:i:s \G\M\T", $last_mod));
//страница не будет меняться 10 минут
header("Expires: " . gmdate("D, d M Y H:i:s \G\M\T", time()+60*10));
}else{
//метка совпала, значит, файл не менялся
header("HTTP/1.1 304 Not Modified");
header("ETag: \"". $etag_server ."\"");
// последнее изменение
header ("Last-Modified: " . gmdate("D, d M Y H:i:s \G\M\T", $last_mod));
//страница не будет меняться 10 минут
header("Expires: " . gmdate("D, d M Y H:i:s \G\M\T", time()+60*10));
}
}
}//if(ctype_digit
}else{
//условных запросов нет, заголовок - предварит. усл. не выполнены
header("HTTP/1.1 412 Precondition Failed");
header("ETag: \"". $etag_server ."\"");
// последнее изменение
header ("Last-Modified: " . gmdate("D, d M Y H:i:s \G\M\T", $last_mod));
//страница не будет меняться 10 минут
header("Expires: " . gmdate("D, d M Y H:i:s \G\M\T", time()+60*10));
exit();
}
//определяем диапазон
$range=substr($_SERVER['HTTP_RANGE'], strpos($_SERVER['HTTP_RANGE'], '=')+1);
$range = substr($range, strpos($range,0,"/"));
$range = explode("-", $range);
$from = $range[0];
$to = $range[1];
//если первая позиция не задана, берем ее с нуля
if($from < 0) $from = 0;
if($to) $to = ++ $to;
//если не задана позиция конца
else $to = $size;
//от позиции и до конца, диапазон типа 521000-
if($range[1]==''){
$from = $range[0];
$to = $size;
}
//последние байты, диапазон типа -300000
if($range[0]==''){
$from = $range[1];
$to = $size;
}
//узнаем, запросил клиент полный файл либо кусок
if ($from > 0 || $to < $size) {
//первый и последний байт куска
$start = $from;
$end = $to - 1;
header('HTTP/1.1 206 Partial Content');
//длина диапазона, который считывается
$part = $to - $from;
header('Content-Length:' . $part);
header('Content-Range: bytes ' . $start . "'-'" . $end . "'/'" . $size);
header("ETag: \"". $etag_server ."\"");
// последнее изменение
header ("Last-Modified: " . gmdate("D, d M Y H:i:s \G\M\T", $last_mod));
//страница не будет меняться 10 минут
header("Expires: " . gmdate("D, d M Y H:i:s \G\M\T", time()+60*10));
}else{
// если клиент не запросил докачку или byte-range-spec некорректна
header("HTTP/1.1 200 OK");
header('Content-Length:' . $size);
header("ETag: \"". $etag_server ."\"");
// последнее изменение
header ("Last-Modified: " . gmdate("D, d M Y H:i:s \G\M\T", $last_mod));
//страница не будет меняться 10 минут
header("Expires: " . gmdate("D, d M Y H:i:s \G\M\T", time()+60*10));
}
}else{
//диапазон неопределен, значит, клиент запросил весь файл
header("HTTP/1.1 200 OK");
header('Content-Length:' . $size);
header("ETag: \"". $etag_server ."\"");
// последнее изменение
header ("Last-Modified: " . gmdate("D, d M Y H:i:s \G\M\T", $last_mod));
//страница не будет меняться 10 минут
header("Expires: " . gmdate("D, d M Y H:i:s \G\M\T", time()+60*10));
$range = array(0,$size);
$from = $range[0];
$to = $range[1];
$start = $from;
}
//отправляем заголовки и для целого файла, и для частичной загрузки
header('Content-Type: '. $mime);
header('Content-Disposition: attachment; filename=' . basename($filename));
//ставим указатель на начальную позицию, которую задал клиент
fseek($f, $start, SEEK_SET);
//выдача файла
while(!feof($f)) {
if($to == $size){
//выводим остаток до конца файла
fpassthru($f);
}else{
//выводим начало и середину
$read = fread($f, 256000);
echo $read;
flush();
}
}
flock($f, LOCK_UN);
fclose($f);
}//while($row =
//счетчик скачиваний
$sql_update = "UPDATE books SET count_download = count_download + 1 WHERE id =" . $_GET['id_book'];
mysql_query($sql_update);
}//if(isset($_GET
?>
|
| |
|
|
|
|