Вот уже приблизительно два года я пользуюсь услугами хостинг-провайдера Хостинг-Центр РБК. В лист предоставляемых услуг входит возможность создания самостоятельных подсайтов внутри имеющегося домена, на которые накладываются странные ограничения. Те, кто пытался установить WordPress, TYPO3, Joomla и прочие гибко настраиваемые CMS наверняка знают о чём идёт речь.
Проблема заключается в установленных ограничениях на оперативную память для исполняемых скриптов. Поэтому при попытке запустить «громоздкий» скрипт сервер возвращает ошибку.
Fatal error: Out of memory (allocated 9699328)
Разговоры на эту тему с техподдержкой ХЦ бесполезны.
В данной статье речь пойдёт о простом (не идеальном) способе обхода описанной проблемы используя .htaccess и php.
Очевидно то, что для корректной установки и дальнейшей работы CMS необходимо будет запустить её на основном либо другом удалённом хостинге. Затем .htaccess на нашем подсайте будет отлавливать HTTP-запросы и отдавать их нашему php-cкрипту, который будет делать реальные запросы к удалённому сайту, забирать и изменять ссылки в содержимом, а затем его выводить.
Определимся с деталями. Положим что уже есть установленный и работающий WordPress на сайте http://www.site.ru/blog/. На основном хостинге создан подсайт blog.site.ru, где мы хотим видеть наш блог. В настройках веб-сервера нашего подсайта (в контрольной панели Хостинг-Центра РБК) выбрана версия PHP5. Версия движка WordPress – 2.7.1.
.htaccess
Первым действием в публичной папке подсайта («~/www/htdocs/«) создаём (либо изменяем содержимое уже имеющегося файла) «.htaccess» на следующие строки
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
Это стандартный .htaccess-файл движка блогов WordPress, являющийся идеально подходящим как для нашего случая, так и для тех, когда на удалённом сайте установлена любая другая CMS. Данная конфигурация возвращает пользователю, запрашивающему адрес вида http://blog.site.ru/<что угодно> результат работы скрипта index.php. В самом index.php мы уже работаем с запрошенным URL.
В PHP включена поддержка libcurl — библиотеки функций, написанной Daniel Stenberg, которая позволяет взаимодействовать с различными серверами по различным протоколам.
Возможности этой библиотеки мы и будем в основном использовать в нашем скрипте.
Первый подводный камень — невозможность обработки нашим скриптом POST-запросов, а это значит что для наименьших «потерь» (да, они всё-таки предстоят в нашем случае) админкой WordPress‘а придётся пользоваться по реальному адресу. Скорее всего проблема в том, что POST-запросы хранятся в заголовке документа, и при попытке внешнего скрипта (нашего index.php) для нашего реального сайта «подсунуть» ему переменные в POST-запросе останавливают выполнение нашего скрипта из соображений безопасности. Формы на сайте использующие метод GET (например тот же поиск) работать будут нормально.
index.php
Метод работы cURL в PHP делится на 2 части: случаи, когда мы работаем с заголовком запрашиваемого документа, и случаи, когда мы работаем с его телом. В нашем случае мы действуем сразу в обоих направлениях.
Итак, второе наше действие — создаём index.php.
Прежде всего определимся с переменными GET-форм. Передавать их в запросе следует используя специальный параметр сеанса cURL — CURLOPT_POSTFIELDS. Значение этого параметра — собственно сами данные POST-запроса известного вида «?key=value&key2=value2«. Наш скрипт получает переменные эти в виде массива $_POST.
foreach ($_POST as $key => $value) {
$postapx .= "&" . $key . '=' . $value;
}
Этот фрагмент собирает все полученные скриптом переменные в строку ($postapx), которую мы будем присоединять к нашим запросам реальной страницы.
Как говорилось выше, работа сURL разделена на две части: возврат заголовка и возврат содержимого запрашиваемого документа. Прежде чем выводить содержимое, необходимо скопировать заголовок удалённой страницы.
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://www.site.ru/blog" . $_SERVER["REQUEST_URI"]);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_NOBODY, 1);
if ($postapx) {
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postapx);
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$resp = curl_exec($ch);
curl_close($ch);
Инициализируем сеанс cURL и задаём ему параметры. CURLOPT_URL — указание запрашиваемой страницы. CURLOPT_HEADER — переключатель установленный в значение «включен» (по умолчанию «выключен»), отвечающий за возврат в результат заголовка. CURLOPT_NOBODY — переключатель, отключающий включение в результат запроса содержимого документа, потому как мы заняты пока исключительно заголовками. Затем мы проверяем наличие переменных GET-запроса нашему скрипту и при положительном результате включаем их в запрос удалённого документа. Положительное значение переключателя CURL_RETURNTRANSFER запрещает прямой вывод результата запроса, вместо чего возвращает его в виде значения. Полезно, когда мы хотим записать его в переменную ($resp) для дальнейших операций над результатом. Все необходимые действия выполнены. Закрываем сеанс.
$headLines = explode("\n", $resp);
$i = 0;
foreach ($headLines as &$value) {
$i++;
if ($i > 2) {
$skip = false;
if (
strstr($value, "Connection: keep-alive") ||
strstr($value, "Keep-Alive:") ||
strstr($value, "Transfer-Encoding:")
)
$skip = true;
if (!$skip)
header($value);
}
}
Данные заголовка запрошенного документа сейчас хранятся в исходном виде — каждый новый параметр заголовка разделяется символом переноса строки \n. Существует необходимость работать с каждым параметром отдельно. Первая строка разбивает каждую новую строку принятой информации в отдельные элементы массива оператором explode. Далее наш скрипт перебирает каждый параметр на предмет необходимости включения его в наш документ.
HTTP/1.1 200 OK
Server: nginx/0.5.35
Первые две строки наш скрипт вернёт и без их наследования, поэтому организованный перед циклом счётчик $i пропускает обработку первых двух строк результата. Есть ещё несколько параметров (Connection, Keep-Alive и Transfer-Encoding) которые мы не наследуем из-за неуместности их при работе со статичными веб-документами.
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://www.site.ru/blog" . $_SERVER["REQUEST_URI"] . $postapx);
if ($postapx) {
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postapx);
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$content = curl_exec($ch);
curl_close($ch);
Снова инициализируем cURL-сессию, теперь уже для записи содержимого запрашиваемого документа в переменную $content.
$content = str_replace('http://www.site.ru/blog', 'http://blog.site.ru', $content);
print $content;
Затем производим в результате запроса замену всех присутствующих в нём ссылок на удалённые ресурсы и выводим его.
На этом этапе можно проверить работоспособность нашего скрипта. Но помните о рассказанных ранее проблемах с POST-запросами. Далее речь пойдёт лишь о частном случае с WordPress.
А теперь о жертвах
Сразу нужно упомянуть о том, что единственную жертву мы имеем лишь в случае, когда мы не используем других попыток создать POST-запрос нашему скрипту. В ином случае жертвами становятся все файлы, делающие эти попытки. Таким образом логично выключить из шаблона ссылки для входа и регистрации в административной зоне WordPress (вход и регистрация), т.к. всё-равно функционировать они не будут.
Как следствие: Вы публикуете новый материал и вообще пользуетесь админкой по реальному её адресу вида http://www.site.ru/blog/wp-login.php.
Публично POST-запрос в блоге также используется, когда пользователь пытается оставить комментарий к записи. Следовательно наша жертва – файл wp-comments-post.php. Он начинается со следующего блока:
if ( 'POST' != $_SERVER['REQUEST_METHOD'] ) {
header('Allow: POST');
header('HTTP/1.1 405 Method Not Allowed');
header('Content-Type: text/plain');
exit;
}
Он полностью комментируется. Затем методом поиска и замены ищем все фрагменты «$_POST» заменяя их на «$_GET«.
Нужно помнить об этих изменениях при обновлениях движка WordPress эти изменения аннулируются.
Последний этап — изменение метода формы обращённой к только что изменённому файлу. В случае с использованием стандартной темы Kubrick она находится в файле comments.php в каталоге темы.
В нём ищем:
<form action="<?php echo get_option('siteurl'); ?>/wp-comments-post.php" method="POST" id="commentform">
… и заменяем method="POST" на method="GET".
Вот и всё!
Архив содержащий скрипт и изменённый wp-comment-post.php (2,459 b)
UPD от 15.07.2009:
Яндекс снял с индекса… Пожалуй есть причины переселить блог на другой хостинг.
Anagh
02.08.2009 в 10:59
Спасибо тебе большое. Без этой статьи, у меня никогда бы не получилось поставить wordpress на хостинг РБК.