Pull to refresh

Проксируем и спасаем

Reading time 7 min
Views 159K
1 ноября мир изменился и больше никогда не будет таким же как прежде. В российском интернете появилась цензура — общеизвестный уже список запрещенных сайтов. Для одних это важнейшая политическая тема, для других повод изучить технологии шифрования и защиты анонимности, для третьих просто очередной странный закон, который приходится исполнять на бегу. Мы же поговорим о технологическом аспекте.

В данном пособии мы узнаем как быстро и просто сделать рабочее зеркало любого сайта, что позволяет сменить IP и назначить любое доменное имя. Мы даже попробуем спрятать домен в url, после чего можно сохранить локально полную копию сайта. Все упражнения можно сделать на любом виртуальном сервере — лично я использую хостинг Хетцнер и OS Debian. И конечно мы будем использовать лучший веб-сервер всех времен и народов — NGINX!

К этому абзацу пытливый читатель уже приобрел и настроил какой нибудь выделенный сервер или просто запустил Linux на старом компьютере под столом, а так же запустил Nginx последней версии со страничкой «Save me now».


Перед началом работы необходимо скомпилировать nginx c модулем ngx_http_substitutions_filter_module, прежнее название — substitutions4nginx.

Дальнейшая конфигурация будет показана на примере сайта www.6pm.com. Это сайт популярного онлайн магазина, торгующего товарами с хорошими скидками. Он отличается категорическим нежеланием давать доступ покупателям из России. Ну чем не оскал цензуры капитализма?

У нас уже есть работающий Nginx, который занимается полезными делом — крутит сайт на системе Livestreet о преимуществах зарубежного шоппинга. Чтобы поднять зеркало 6pm прописываем DNS запись с именем 6pm.pokupki-usa.ru который адресует на IP сервера. Как вы понимаете, выбор имени для суб-домена совершенно произволен. Это имя будет устанавливаться в поле HOST при каждом обращении к нашему новому ресурсу, благодаря чему на Nginx можно будет запустить виртуальный хостинг.

В корневой секции конфигурации nginx прописываем upstream — имя сайта-донора, так будем его называть в дальнейшем. В стандартных гайдах сайт обычно называется back-end, а reverse-proxy называется front-end.

http {
    ...
    upstream 6pm { server www.6pm.com; }


Дальше нужно создать секцию server, вот как она выглядит

    server {
        listen          80;
        server_name     6pm.pokupki-usa.ru;
        limit_conn  gulag 64;

        access_log   /var/log/nginx/6pm.access.log;
        error_log    /var/log/nginx/6pm.error.log;

        location / {
            root /var/www/6pm;
            try_files $uri @static;
        }
        location @static {

            include '6pm.conf';
            proxy_cookie_domain 6pm.com 6pm.pokupki-usa.ru;

            proxy_set_header Accept-Encoding "";
            proxy_set_header      Host     www.6pm.com;
            proxy_pass http://6pm;

            proxy_redirect http://www.6pm.com http://6pm.pokupki-usa.ru;
            proxy_redirect https://secure-www.6pm.com https://6pm.pokupki-usa.ru;
        }
    }



Стандартные директивы listen и server определяют имя виртуального хоста, при обращении к которому будет срабатывать секция server. Файлы логов лучше сделать отдельными.

Объявляем корневой локейшин, указываем путь до его хранилища — root /var/www/6pm; затем используем try_files. Это очень важная директива nginx, которая позволяет организовать локальное хранилище для загруженных файлов. Директива сначала проверяет нет ли файла с именем $uri и если не находит его — переходит в именованный локейшин @ static
$uri — переменная nginx, которая содержит путь из HTTP запроса

Префикс “@” задаёт именованный location. Такой location не используется при обычной обработке запросов, а предназначен только для перенаправления в него запросов. Такие location’ы не могут быть вложенными и не могут содержать вложенные location’ы


В нашем случае конструкция используется только для подмены файла robots.txt, чтобы запретить индексацию содержимого сайта. Однако таким образом делается зеркалирование и кеширование в nginx.

include '6pm.conf' — логика модуля substitutions.

proxy_cookie_domain — новая функция, которая появилась в nginx версии 1.1.15, без этой директивы приходилось делать так. Больше не нужно ломать голову, прописываете одну строчку и куки просто начинают работать.

proxy_set_header Accept-Encoding ""; — очень важная команда, которая заставляет сайт донор отдавать вам контент не в сжатом виде, иначе модуль substitutions не сможет выполнить замены.

proxy_set_header Host — еще одна важная команда, которая в запросе к сайту донору выставляет правильное поле HOST. Без нее будет подставляться имя нашего прокси сервера и запрос будет ошибочным.
proxy_pass — прямая адресация не работает в именованном локейшине, именно поэтому мы прописали адрес сайта донора в директиве upstream.
proxy_redirect — многие сайты используют редиректы для своих нужд, каждый редирект нужно отловить и перехватить здесь, иначе запрос и клиент уйдет за пределы нашего уютного доменчика.

Теперь посмотрим содержимое 6pm.conf. Я не случайно вынес логику трансформации в отдельный файл. В нем можно разместить без какой либо потери производительности тысячи правил замены и сотни килобайт фильтров. В нашем случае мы хотим лишь завершить процесс проксирования, поэтому файл содержит всего 5 строк:

Меняем коды google analytics:
subs_filter 'UA-8814898-13' 'UA-28370154-3' gi;
subs_filter "'.6pm.com']," "'6pm.pokupki-usa.ru']," gi; 

Уверяю, что это самая безобидная шалость из возможных. У нас появится статистика посещений, а у сайта донора эти визиты — исчезнут.

Меняем все прямые ссылки на новые.
subs_filter "www.6pm.com" "6pm.pokupki-usa.ru" gi;
subs_filter "6pm.com" "6pm.pokupki-usa.ru" gi;


Как правило, в нормальных сайтах все картинки лежат на CDN сетях, которые не утруждают себя проверкой источника запросов, поэтому достаточно замены ссылок только основного домена. В нашем случае 6pm выпендрился и разместил часть картинок на доменах, которые отказывают посетителям из России. К счастью, модуль замены поддерживает регулярные выражения и не составляет никакого труда написать общее правило для группы ссылок. В нашем случае обошлось даже без regexp, просто поменяли два символа в домене. Получилось так:

subs_filter "http://a..zassets.com" "http://l3.zassets.com" gi;


Единственное, но очень серьезное ограничение модуля замены — он работает только с одной строкой. Это ограничение заложено архитектурно, поскольку модуль работает на этапе, когда страница загружена частично (chunked transfer encoding) и нет никакой возможности выполнить полнотекстовый regexp.

Все, можно посмотреть на результат, все работает, даже оплата заказа проходит без затруднений.

Итак, мы перекинули сайт на новый IP адрес и новый домен. Это было простой задачей. Можно ли запроксировать сайт не в новый домен, а в поддиректорию существующего? Это сделать можно, но возникают сложности. Для начала вспомним какие бывают html ссылки:
  1. Абсолютные ссылки вида «www.example.com/some/path»
  2. Ссылки относительно корня сайта вида "/some/path"
  3. Относительные ссылки вида «some/path»


С п.1 все просто — мы заменяем все ссылки на новый путь с поддиректорией
С п.3 так же просто — мы ничего не трогаем и все работает само если не использовался атрибут base href. Если этот атрибут используется, что бывает крайне редко в современных сайтах, то достаточно его заменить и все будет работать.

Настоящая сложность возникает с п.2. из-за того что мы должны изменить кучу ссылок вида /... на /subdomain/.... Если сделать это в лоб, то сайт скорее всего полностью перестанет работать, ибо такая замена сломает множество конструкций использующих слеш, что испортит почти все скрипты javascript.

Теоретически можно написать достаточно общий универсальный regexp, который сумеет выбрать исключительно нужные паттерны для замены, на практике гораздо проще написать несколько простых regexpов, которые будут по частям переводить нужные ссылки.

Вернемся к нашему пациенту:

        location /6pm {
            root /var/www/6pm;
            try_files $uri @6pm-static;
            access_log   /var/log/nginx/6pm.access.log;
        }
        location @6pm-static {
            include '6pm2.conf';
            proxy_cookie_domain 6pm.com pokupki-usa.ru;
            proxy_cookie_path / /6pm/;

            rewrite ^/6pm/(.*) /$1 break;

            proxy_set_header Accept-Encoding "";
            proxy_set_header      Host     www.6pm.com;
            proxy_pass http://6pm;

            proxy_redirect http://www.6pm.com http://pokupki-usa.ru/6pm;
            proxy_redirect http://www.6pm.com/login http://pokupki-usa.ru/6pm;
            proxy_redirect https://secure-www.6pm.com https://pokupki-usa.ru/6pm;


Конфигурация сервера претерпела некоторые изменения.

Во-первых, вся логика перенесена из директивы sever напрямую в location. Нетрудно догадаться, что мы решили создать директорию /6pm в которую будем выводить проксируемый сайт.

proxy_cookie_path / /6pm/ — переносим куки из корня сайта в поддиректорию. Это делать не обязательно, но в случае если проксируемых сайтов окажется много, их куки могут пересечься и затереть друг друга.

rewrite ^/6pm/(.*) /$1 break; — эта магия вырезает из клиентского запроса поддиректорию, которую мы добавили, в результате директива proxy_pass отправляет на сервер-донор корректное значение.

Чуть сложнее стало ловить редиректы. Теперь все ссылки на корень нужно перебросить на /6pm.

Посмотрим на логику трансформации:

subs_filter_types text/css text/javascript;

# Fix direct links
subs_filter "http://6pm.com" "http://pokupki-usa.ru/6pm" gi;
subs_filter "http://www.6pm.com" "http://pokupki-usa.ru/6pm" gi;

# Fix absolute links
subs_filter 'src="/' 'src="/6pm/' gi;
subs_filter 'href="/' 'href="/6pm/' gi;
subs_filter 'action="/' 'href="/6pm/' gi;

# Fix some js
subs_filter "\"/le.cgi" "\"/6pm/le.cgi" gi;
subs_filter "\"/track.cgi" "\"/6pm/track.cgi" gi;
subs_filter "\"/onload.cgi" "\"/6pm/onload.cgi" gi;
subs_filter "\"/karakoram" "\"/6pm/karakoram" gi;
subs_filter "/tealeaf/tealeaf.cgi" "/6pm/tealeaf/tealeaf.cgi" gi;

# Css and js path
subs_filter "script\('/" "script('/6pm/" gi;
subs_filter "url\(/" "url(/6pm/" gi;

subs_filter 'UA-8814898-13' 'UA-28370154-3' gi;
subs_filter "'.6pm.com']," "'pokupki-usa.ru/6pm']," gi;

subs_filter "http://a..zassets.com" "http://l3.zassets.com" gi;


Во-первых, мы включили фильтрацию файлов css и javascript (парсинг html включен по-умолчанию)
Во-вторых, начинаем аккуратно находить и заменять разные типы ссылок относительно корня. Нам попался средней сложности сайт, в котором часть скриптов содержат такие пути.

В итоге получилось так: http://pokupki-usa.ru/6pm/

К сожалению, мне не удалось до конца написать фильтр для случая поддиректории. Я не дошел до преобразования динамических запросов скриптов корзины с покупками, хотя не сомневаюсь что это решаемо. Просто моих знаний в Javascript не достаточно чтобы выполнить необходимую отладку, буду рад советам как запустить корзину покупок, которая сейчас в упомянутом примере не работает.

В любом случае, это, возможно, первый гайд, в котором описан метод проксирования в поддиректорию.
Tags:
Hubs:
+63
Comments 25
Comments Comments 25

Articles