Архив за Апрель 2009

Ограничение скорости скачивания файлов средствами PHP

Допустим, у нас есть сайт, предоставляющий файлы для скачивания. Но для зарегистрированных пользователей нет никаких ограничений, а для остальных, чтобы не перегружать сервер, хотелось бы ввести некоторые ограничения. Причина для ограничения скорости скачивания может быть и иной: пользователь, оплативший скачивание без ограничений - получает файл на максимальной скорости, а тот, кто пожадничал - будет скачивать очень медленно. Посмотрим, как это можно реализовать с помощью PHP:

<?php
// лимит времени выполнения
set_time_limit(0);

// скорость скачивания - 128КБ в сек.
$speed = 1024*128;
// имя файла
$filename = 'doc.pdf';
// размер файла
$filesize = filesize($filename);
// смещение от начала файла
$range = 0;

while( is_already_download() ) {
  // спим пока у пользователя есть активные потоки
  sleep(1);
}

// открываем файл на чтение
$f = fopen($filename, 'rb');

if (isset($_SERVER['HTTP_RANGE'])) { // поддерживается ли докачка
  $range = $_SERVER['HTTP_RANGE'];
  $range = str_replace('bytes=', '', $range);
  $range = str_replace('-', '', $range);
  if ($range) fseek($f, $range);
}
 
// если есть смещение
if ($range) {
  header($_SERVER['SERVER_PROTOCOL'].' 206 Partial Content');
} else {
  header($_SERVER['SERVER_PROTOCOL'].' 200 OK');
}

header( 'Last-Modified: '.date('D, d M Y H:i:s T', filemtime($filename)) );
header('Content-Length: '.($filesize-$range));
header('Accept-Ranges: bytes');
header('Content-Range: bytes '.$range.'-'.($filesize - 1).'/'.$filesize);
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="'.$filename.'"');

while( !feof($f) ) {
  echo fread($f, $speed);
  flush();
  sleep(1); // засыпаем
}

// закрываем файл
fclose($f);

// удаляем информацию о соединении из БД
mysql_query("DELETE FROM `sessions` WHERE `session_ip`='".$_SERVER['REMOTE_ADDR']."' LIMIT 1");

function is_already_download() {
  // проверяем на наличие соединений от пользователя
  $res = mysql_query("SELECT `session_ip` FROM `sessions` WHERE `session_ip`='".$_SERVER['REMOTE_ADDR']."' LIMIT 1");
  if (mysql_num_rows($res)) {
    return true;
  } else { // если запись отсутствует, то добавляем
    mysql_query ("INSERT INTO `sessions` VALUES ('".$_SERVER['REMOTE_ADDR']."')");
    return false;
  }
}
?>

Однако пользователь без привилегий может воспользоваться менеджером закачки, который позволяет скачивать файл в несколько потоков. Таким образом, он сможет без труда обойти наше ограничение. На этот случай есть функция is_already_download(), которая проверяет наличие уже установленных соединений.

В данном случае мы используем таблицу БД sessions, в которй всего одно поле - IP-адрес скачивающего. При наличии IP-адреса скачивающего в таблице отдаем true, в противном случае записываем его и отдаем false.

Ссылки по теме:

Восстановление разрушенных таблиц MySQL

Бывает, что файлы данных MySQL разрушаются и требуют восстановления. Проще всего проверить целостность таблицы путем выполнения команды

CHECK TABLE messages;

Это выражение проверит наличие ошибок в таблице messages. Если выходные данные выглядят подобно приведенным ниже, значит, таблица в порядке, и действия по ее восстановлению выполнять не нужно.

В некоторых случаях в столбце Msg_text появляются сообщения об ошибках. В этом случае надо выполнить команду REPAIR TABLE, и MySQL предпримет попытку устранить проблему.

Утилита myisamchk, поставляемая с MySQL, позволяет проверять и восстанавливать таблицы MyISAM, обеспечивая при этом более высокую степень гибкости по сравнению с командами SQL. Если при запуске myisamchk не указаны опции, данная программа лишь проверяет таблицу на наличие ошибок. Ряд опций командной строки позволяют получить дополнительную информацию или указать утилите на то, что необходимо начать процесс восстановления данных.

Файлы с таблицами MyISAM, проверяемые утилитой myisamchk, находятся в каталоге, предназначенном для хранения данных, и имеют расширение .MYI. Если данные размещены в каталоге /var/lib/mysql, вы можете проверить все таблицы в базе forum с помощью команды:

myisamchk /var/lib/mysql/forum/*.MYI

Как правило, для быстрой проверки используется опция -fast. Она указывает на то, что проверке подлежат лишь те таблицы, которые были закрыты не корректно.

Опция –medium-check задает более детальную проверку таблиц, при которой могут быть найдены разнообразные ошибки. Самая тщательная проверка осуществляется при указании опции –extend-check, но она выполняется очень медленно. Данная опция используется только в том случае, если –medium-check не позволяет выявить проблему.

После того, как вы выяснили, что таблица разрушена, можно приступить к ее восстановлению. Для этой цели служит опция –recover. Перед тем как пытаться восстановить таблицу с помощью myisamchk, вам надо остановить mysqld. Если сервер выполнит запись в таблицу в то время, когда осуществляются действия по ее восстановлению, результаты могут быть непредсказуемыми.

В некоторых случаях программа myisamchk сообщает о том, что в данном режиме она не может устранить проблему, и вам необходимо задать опцию –safe-recover. При указании этой опции осуществляются дополнительные операции по восстановлению данных, но работа существенно замедляется.

Как избавиться от сообщений “headers already sent”

Предположим, вы пытаетесь отправить HTTP-заголовок или cookie с помощью функции header(), session_start() или setcookie(), но PHP выдает сообщение “headers already sent” (”заголовки уже отправлены”). Эта ошибка возникает, если вы отправили содержимое браузеру до вызова функции header(), session_start() или setcookie(). PHP посылает заголовки автоматически, как только скрипт начинает выдавать браузеру информацию. Перепишите свой код так, чтобы вывод содержимого происходил после отправки заголовков:

// Правильно
setcookie("name", $name);
echo "Hello, $name";
// Неправильно
echo "Hello, $name";
setcookie("name", $name);
// Правильно
<?php setcookie("name", $name); ?>
<html><title>Hello</title>

Любое HTTP-сообщение имеет заголовок и тело, которые отправляются браузеру именно в таком порядке. Начав отправку тела, вы больше не можете отправлять заголовки. Поэтому, если вы вызовете функцию setcookie() после вывода HTML-кода, PHP не сможет отправить надлежащий заголовок cookie.

Вы также должны убрать из подключеных файлов завершающие пробельные символы. Когда вы подключаете php-файл с пустыми строчками вне тегов <?php ?>, эти строчки отправляются браузеру.

Другой способ гарантировать отсутствие завершающих пробельных символов в подключаемом файле является отказ от использования закрывающего тега ?>. Если подключаемый файл содержит только PHP-код, этот метод избавит вас от необходимости возвращаться к этому файлу и удалять из него нечаянно поставленные пробельные символы.

Если вы хотите полностью избавить себя от беспокойства по поводу пустых строчек, вызывающих отправку заголовков, включите буферизацию вывода. Она не позволит PHP немедленно отправлять вывод клиенту. Если вы буферизируете свой вывод, то можете перемешивать заголовки и текст сообщений. Однако в этом случае у пользователей может возникнуть впечатление, что ваш сервер медленно обслуживает их запросы, потому что браузер будет выводить содержимое на экран с некоторой задержкой.