Проксируем Cookies на Nginx при помощи модуля lua-nginx



    Я уже писал о том, как с помощью Nginx трансформировать контент на лету. С момента публикации статьи на базе описанного метода запущен и развивается реальный проект ecommerce. Помимо перевода и трансформации также реализован и SEO рерайт по заветам руководства для начинающих от Google.

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

    В чем суть проблемы


    Проблема заключается в том, что любой нормальный сервер приложений всегда выставляет Cookie, например для того, чтобы сохранять сессию клиента или корзину с его товаром. Если этот сервер (точнее его администратор) озабочен поддержанием определенного уровня безопасности, то он выставляет в теле Cookie домен и путь, например domain= backend.org; path=/path1. Наш Nginx запущенный в режиме Reverse Proxy замечательно меняет все ссылки в теле документов с backend.org на frontend.org, но не делает этого для кук! Это означает что браузер клиента отвергнет такие куки.

    Этот вопрос с давних пор волнует умы администраторов nginx, в рассылках он всплывает по 1-2 раза в год. Большинство вопрошавших, по-видимому, решили свои проблемы подкручивая логику backendа, но не я! После очередного апдейта оригинального сайта стало понятно, что костыль с PHP + Curl тянуть больше невозможно и надо непременно найти решение с помощью Nginx!

    Я вернул тему в рассылку, попутно перебирая варианты из ngx_http_perl_module и переменной $upstream_http_set_cookie, даже заглянул в дебри сорсов с призрачной надеждой написать модуль самому. Но все было неудачно пока в один прекрасный момент я не получил письмо от Mikhail Mazursky, который дал ценный совет. Благодаря этому совету я не только с легкостью решил задачу проксирования Cookie, но и получил новый инструмент, с помощью которого можно создать версию 2.0 своего проекта.

    Решение


    Название этого инструмента lua-nginx-module, который написан еще одним китайским самородком с корнями из Taobao. Из названия легко понять, что речь об языке скриптов Lua встроенном в Nginx — но это больше чем просто интерпретатор! Эти ребята создали полностью неблокируемую реализацию с производительностью десятки тысяч операций в секунду, которая имеет хуки ко всем событиям внутри Nginx. То что раньше можно было реализовать только написав свой модуль на C, теперь можно сделать несколькими строчками на Lua. Заинтересовались?


    Установка


    Для начала качаем свежую версию Nginx cами знаете откуда. Вдруг кто забыл?

    Затем качаем свежую версию lua-nginx-module со страницы проекта.

    Потребуются библиотеки:

    apt-get install libpcre3 libpcre3-dev libperl-dev lua5.1 liblua5.1-dev

    Как собирать Nginx должны бы уже знать наизусть, глядя на скорость выпуска новые релизы. Приведу конфиг из моего кукбука:

    ./configure --with-cc-opt="-I /usr/include" --with-ld-opt="-L /usr/lib" --with-http_sub_module --with-http_ssl_module --prefix=/home/nginx/data --user=nginx --group=nginx --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/ --add-module=../substitutions4nginx/ --add-module=../chaoslawful-lua-nginx-module-*


    Конфигурация


    После make install не забываем перезапустить nginx. И смотрим что же получилось? А получилась вот такая великолепная штука: http://wiki.nginx.org/HttpLuaModule, которая по уверению его создателей на среднем настольном ПК тянет 25 тысяч запросов в секунду. Внимательно изучив документацию становится понятно, что в среде исполнения Lua доступны все события и внутренние обработчики Nginx. Конкретно для нашей задачи мы будем использовать директиву header_filter_by_lua . Открываем конфигурационный файл, выбираем нужный location и добавляем простой код:

    header_filter_by_lua '
            local headers = ngx.header["Set-Cookie"]
            if headers then
                if type(headers) == "string" then
                    headers = {headers}
                end
                for i, header in ipairs(headers) do
                    local cookie = ngx.re.match(header, "JSESSIONID=([^;]+);", "io")
                    if cookie then
                        headers[i] = "JSESSIONID=" .. cookie[1] .. "; domain=.frontend.com; path=/newpath;"
                    end
                end
                ngx.header["Set-Cookie"] = headers
            end
    ';
    


    Тут приведен самый простой вариант обработчика — первый код на lua который я увидел в своей жизни. Можно и нужно написать универсальный парсер хедеров Set-Cookie, страна уже ждет своего героя в комментариях!

    Что дальше?


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

    1. Счетчик посещений. Счетчик внутри самого вебсервера будет избавлен от кучи програмного кода благодаря прямому доступу к нужным переменным. Если подключить NoSQL систему типа Redis прямо к Nginx, то мы избавимся и от кучи SQL кода. Это будет совершенно незаметный и чертовски быстрый счетчик!
    2. Анти-ДДОС. C помощью языка, на котором пишется AI большинства современных игр легко делать интеллектуальный фильтр нежелательных запросов к веб-серверу.
    3. Автоматический генератор SEO реврайтов. Вместо ручного прописывания правил и добавления метатегов — пишем скрипт, который генерирует метатеги на лету, например как в модуле для livestreet.
    4. И, конечно, перевод-2.0, с элементами распознавания html блоков и избирательной фильтрации содержимого.


    А что придумал ты, уважаемый хабраюзер?

    P.S. Скучный пост, совсем нет картинок. На правах анонса добавлю картинку, как SEO рерайт при помощи Nginx изменил позицию в поиске Google. Нужна про это статья?

    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 18
    • +5
      Картинка к посту вызывает странные ассоциации.
      • 0
        Новая картинка лучше?
        • 0
          По мне старая лучше.

          P.S. неблокируемую неблокирующую
          • 0
            Да и старая плохой не была. Даже всесело получилось :)
            • 0
              За старую стеcнялись голосовать :)
        • 0
          Если домен передается на backend через HTTP заголовок Host, то почему скрипты на этом самом backend'е это не используют? Или я чего-то очень сильно не понимаю?
          • 0
            Если есть доступ к backend и на нем есть возможность выставить нужную настройка, то проблемы нет. Целых два «если».
            • 0
              А можно увидеть реальный пример, когда это невозможно? Изначально же backend же должен реагировать на заголовок Host?..
              • 0
                Я понял вопрос. На бэкенд передается заголовок Host из запроса клиента, в нем изначально имя фронтэнда и его проксирует Nginx. Бэкенд реагирут на заголовок Host, но в нем уже содержится имя бэкенда.

                Вы предлагаете:

                а) Отключить проксирование заголовка Host в Nginx
                б) СДелать чтобы бэкенд выдавал домен взятый из хоста.

                Мне кажется что реализация а) прямо сломает работоспособность бекенда, например по заголовку host определяется Virtual Server

                Реализация б) потенциальное отверстие в защите
                Тоже самое — просто не устанавливать поле домена в куки.

                За примером далеко ходить не нужно, вот ответ главной habrahabra

                Set-Cookie:hfl=top; expires=Mon, 21-Nov-2011 09:46:33 GMT; path=/; domain=.habrahabr.ru

                Если вы запроксируете хабр через Nginx, то несмотря на имя вашего сайта my-blackjack-habr-with-bitches.ru в кукак будет по прежнему domain=.habrahabr.ru Не будет работать авторизация и прочий функционал, который требует рабочих кук (может и к лучшему?)

                Вы кончено можете обратится к админам и попросить выставлять домен .my-blackjack-habr-with-bitches.ru, или снять вообще эту установку, но есть подозрения что добиться реакции будет несколько сложнее, чем написать несколько строчек на Lua.
                • 0
                  Пишем в конфиге nginx'a «proxy_set_header Host $host;» После этого на backend будет передаваться тот же заголовок Host, что и был передан на frontend. Ровно также все работает, если nginx'a вообще нет.

                  У меня все это прекрасно работает и в случае с Apache, и с Tomcat.
          • +4
            Поделюсь полезным знанием, как не прибегая к Lua и mod_sub заставить счастливо жить фронтэнд на nginx и бэкенд на Tomcat (в данном случае с jira)
            Nginx слушает на порту 443 (https) и проксирует на порт 8080 к Tomcat'у. Чтобы приложение (Jira) не думало себе лишнего о том, на каком оно хостнэйме и порту живет, прописываем в конфиге Tomcat

            <Connector port="8080"
            ...
            scheme="https" secure="false"
            proxyName="frontend.host.name.here" proxyPort="443"
            ...

            и проблемы с ajax вас не будут беспокоить
            • 0
              а можно чуть подробнее про ajax проблемы с томкатом через нгинкс?
              • +2
                Урл для AJAX запроса часто генерируется приложением, а не определяется в JS из текущей схемы и адреса. В таком случае приложение определяет схему исходя из переменных, переданных ему веб сервером. Ну и если внутри страницы, запрошенной браузером через https, делается запрос к http, то JS возвратит ошибку. Указанный вариант обманывает приложение, говоря что схема запроса — HTTPS.
          • +3
            А всё-таки вы откопали очень интересную штуковину! Действительно, можно столько полезного на этом сделать…
            • НЛО прилетело и опубликовало эту надпись здесь
              • +4
                Зарождается новая профессия — «Nginx — программист».
                • 0
                  А теперь и про SEO-рерайт напишите :)

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.