четверг, 3 марта 2011 г.

Ещё пара полезных декораторов

В веб-приложениях для отдачи статических файлов принято использовать возможности веб-серверов (Apache, Nginx), поскольку традиционные языки веб-разработки (Python, Ruby, PHP, Perl) справляются с этой задачей очень плохо. Однако, это связано с рядом ограничений. Во-первых, нельзя автоматически генерировать ссылки на основе информации о маршрутах. Но если это ещё можно пережить (хотя, честное слово, очень неудобно), то есть куда более серьёзная проблема - часто нужен "управляемый" доступ к статическим файлам. Например, скрипт должен проверить права доступа. Для решения этой дилеммы была придумана техника, когда скрипт выполняет свою логику, а реальную передачу файла делегирует веб-серверу путем настройки специальных заголовков ответа. Для nginx это X-Accel-Redirect, а для Apache - X-Sendfile.

При разработке на Common Lisp проблема отдачи статических файлов стоит значительнее менее остро, ибо язык сам по себе намного быстрее и Hunchentoot справляется с этой работой достаточно быстро. Я для публикации статических файлов пользуюсь restas-directory-publisher - модулем для RESTAS. Однако, следует признать, что возможны ситуации, когда отдавать статику средствами CL окажется не очень разумно.

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

Ниже я приведу возможные примеры использования этих декораторов в пакете restas-doc, обеспечивающего работу http://restas.lisper.ru/.

для публикации файлов я использую такой код:
(restas:mount-submodule -publisher- (#:restas.directory-publisher))
При этом, сами файлы с документацией лежат в каталоге /usr/share/doc/restas/.

@apache-xsendfile
При работе с Apache всё просто:
(restas:mount-submodule -publisher- (#:restas.directory-publisher restas:@apache-xsendfile))
а в конфиге Apache необходимо установить значение параметра XSendFilePath таким образом, что бы публикуемые файлы оказались внутри указанного каталога. Самый простой путь
XSendFilePath /
Но это может оказаться не очень разумным с точки зрения администрирования, так что в данном случае можно и так
XSendFilePath /usr/share/doc/restas/
Более подробная информация о настройке сервера есть в официальной документации.

@nginx-accel-redirect
Настройка nginx является более сложной. Необходимо создать internal-секцию и дальше есть два варианта: либо установить root, либо задать alias. В зависимости от выбранного варианта необходимо различным образом формировать заголовок X-Accel-Redirect. Для поддержки этих вариантов в RESTAS добавлено три переменных:
(defvar *nginx-internal-location* nil)
(defvar *nginx-internal-alias* nil)
(defvar *nginx-internal-root* nil)
и их надо настроить точно в соответствии с настройками nginx. При этом, переменные *nginx-internal-alias* и *nginx-internal-root* являются взаимоисключающими и одна из них должна быть установлена в NIL. В принципе, их можно выставить глобально, но RESTAS позволяет сделать лучше. Например, для такого конфига nginx:

location /restas {
internal;
root /usr/share/doc;
}
Код на CL может быть таким:
(restas:mount-submodule -publisher- (#:restas.directory-publisher restas:@nginx-accel-redirect)
(restas:*nginx-internal-location* #P"/restas/")
(restas:*nginx-internal-root* #P"/usr/share/doc/"))
Настройка с root представляется мне довольно неудобной и если nginx поднят только для CL, то гораздо проще делать так
location /protected/ {
internal;
alias /usr/share/doc/restas/;
}
и
(restas:mount-submodule -publisher- (#:restas.directory-publisher restas:@nginx-accel-redirect)
(restas:*nginx-internal-location* "/protected/")
(restas:*nginx-internal-alias* "/usr/share/doc/restas/"))
Вообще, описанный функционал обеспечивает очень важную возможность работать со статическими файлами средствами CL, например, создавать повторно используемые компоненты, а в случае возникновения необходимости легко подключить механизм XSendFile с помощью подключения нужного декоратора.

Я пока разбирался с этими вопрос успел посмотреть советы по использованию XSendFile с RoR и Django - там это носит вид хаков, жёстко прибивающих код к конкретной конфигурации веб-сервера. Этот факт подкрепил моё мнение, что модель декораторов RESTAS вкупе с возможностями CL (такими, как CLOS) обеспечивает более гибкие и мощные возможности, чем более традиционная схема с middleware-компонентами.

7 комментариев:

  1. Любопытно. Только вот не понял, как получается, что хттп-запрос принимает лисп, а отдаёт Апач? Тот же пхп сам поверх апача сидит, а лисп отдельно, как он перехватывает инициативу?

    ОтветитьУдалить
  2. @Demetrius Cond

    А это на самом деле абсолютно всё равно, сидит ли что-то внутри, подключается через ProxyPass или ещё как. Ведь это внутренний фильтр, который обрабатывает заголовки и когда встречает X-Sendfile вместо передачи контента начинает обрабатывать указанный файл.

    ОтветитьУдалить
  3. @Demetrius Conde

    Хм, или я не правильно понял вопрос? В общем, я использую ProxyPass.

    ОтветитьУдалить
  4. Что-то я не понял, какой смысл в подключении nginx/apache?
    Ведь те же права доступа можно легко проверить средствами Лиспа. Да хоть тот же декоратор повесить.
    ??

    ОтветитьУдалить
  5. @linkfly

    Смысл в скорости и масштабируемости. Как раз всякие права и т.п. я хочу в лиспе обрабатывать. А саму техническую часть отдачи файла клиенту на nginx возложить. Вообще, размещение Hunchentoot за nginx может существенно повысить производительность системы. Если же говорить только об отдаче статики, то например в Hunchentoot только сейчас добавили поддержку Range, да и то, полного соответствия спецификации нет. А Nginx делает это и многое другое хорошо.

    ОтветитьУдалить
  6. Интересно, что есть Range и где об этом можно почитать?
    А что за "техническая часть отдачи файла клиенту"? Что может быть такого у nginx, кроме грамотного кэширования статики?

    ОтветитьУдалить
  7. @linkfly

    Range - это например возможность докачки файлов.
    nginx использует epoll и написан на C

    ОтветитьУдалить