Настоящий веб-сайт на Common Lisp за 9 шагов

    Введение





    Эта вводная статья предназначена для желающих попробовать применить Common Lisp в задачах веб-программирования. Я не буду останавливаться на преимуществах этого языка, за меня это сделал ababo в своем вводном посте Разработка web-приложений на языке Common Lisp (часть первая)

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

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

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

    Для тех, кто любит проматывать скучные процедуры установки — в конце статьи размещена небольшая вкусность, которая, возможно, расширит ваш взгляд на веб-программирование, если до этого момента вы не имели дела с лиспом. Ищите по ключевым словам SLIME и SWANK :)


    Установка последней версии SBCL



    Я использую SBCL как наиболее удобную, распространенную и свободную реализацию Common Lisp, тесно интергрированную со средой Emacs, обеспечивающей удобную и комфортную работу. В репозиториях, как правило, находится не самая последняя версия SBCL, поэтому вы можете скомпилировать его из сорцов или установить бинарник для вашей архитектуры. Последнее тривиально, поэтому в этой статье я опишу компиляцию из исходников.

    Чтобы скомпилировать свежий SBCL можно воспользоваться старым, который мы тут же поставим, используя пакетный менеджер:

    $ apt-get install sbcl
    


    Исходники SBCL можно получить тут: sbcl.sourceforge.net/platform-table.html

    # Скачиваем
    $ wget http://downloads.sourceforge.net/project/sbcl/sbcl/1.0.45/sbcl-1.0.45-source.tar.bz2
    # Распаковывываем архив:
    $ bzip2 -cd sbcl-1.0.45.tar.bz2 | tar xvf -
    # Заходим внутрь каталога и компилим
    $ cd sbcl-1.0.45/
    $ sh make.sh
    # Удаляем старый SBCL поставленный из репозиториев
    $ apt-get remove sbcl
    # Устанавливаем скомпилированный sbcl
    $ sh install.sh
    # Проверяем, все ли в порядке:
    $ sbcl
    This is SBCL 1.0.45, an implementation of ANSI Common Lisp.
    More information about SBCL is available at <http://www.sbcl.org/>.
    
    SBCL is free software, provided as is, with absolutely no warranty.
    It is mostly in the public domain; some portions are provided under
    BSD-style licenses.  See the CREDITS and COPYING files in the
    distribution for more information.
    *
    


    Поздравляю, теперь у вас есть свежая версия sbcl. В данный момент я разворачиваю его вместе с вами на своем сервере, чтобы избежать возможных неточностей и в конце статьи вы сможете убедиться в том, что по крайней мере у меня все заработало. Ну, будем надеяться, что это будет так :)

    Ставим quicklisp



    Для управления библиотеками наиболее часто используются два пакетных менеджера — ASDF и QuickLisp. Последний гораздо более дружелюбен, а первый уже предустановлен вместе с SBCL, поэтому сейчас мы установим себе QuickLisp. На quicklisp.org размещена все вводная информация, поэтому я не буду повторяться и мы перейдем сразу к установке:

    $ wget http://beta.quicklisp.org/quicklisp.lisp
    $ sbcl --load quicklisp.lisp
    * (quicklisp-quickstart:install)
    * (ql:add-to-init-file)
    


    Ставим hunchentoot



    Теперь, когда у нас есть библиотечный менеджер, мы устанавливаем
    веб-сервер hunchentoot, вместе со всеми его зависимостями одной командой:

    * (ql:quickload 'hunchentoot)
    


    Ставим swank и slime на сервер



    «И даже более впечатляющий пример удаленной отладки произошел в миссии NASA «Deep Space 1» в 1998 году. Через полгода после запуска космического корабля, небольшой код на Lisp должен был управлять космическим кораблем в течении двух дней для проведения серии экспериментов. Однако, неуловимое состояние гонки (race condition) в коде не было выявлено при тестировании на земле и было обнаружено уже в космосе. Когда ошибка была выявлена в космосе (100 миллионов миль от Земли) команда смогла произвести диагностику и исправление работающего кода, что позволило завершить эксперимент. Один из программистов сказал об этом следующее:

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


    Я услышал об этом инциденте задолго до того как стал заниматься лиспом сам, и честно говоря воспринял это как байку, которой уже не суждено повториться в наш современный мир многомегабайтных исполняемых файлов и не менее тяжеловесных динамически связываемых библиотек. Однако познакомившись с возможностями удаленного управления лисп-образом я убедился в том, что это ничуть не сложнее чем работать с кодом у себя на машине. И никаких инкрементальных сборок, длительной компиляции или закачек скриптов через ftp — с помощью slime я подключаюсь к работающей системе и могу видеть и менять практически все, например реализовать горячую замену кода, или проинспектировать любой объект, функцию или макрос используя мощные средства интроспекции.

    Как это работает? — спросите вы. Внутри лисп образа на удаленном сервере работает SWANK — специальная библиотека, предоставляющая собой бэкэнд, который предоставляет доступ ко всем рычагам управления лисп образом. SWANK написан на Common Lisp и общается со SLIME по довольно простому текстовому протоколу.

    В моем Emacs-e работает SLIME, написанный на Emacs Lisp, который позволяет мне в момент редактирования файла с кодом отправлять команды, куски кода, определения объектов и структур удаленному образу Common Lisp. Таким образом, вы можете даже вообще не иметь копии исходного кода на удаленном сервере — и в этом случае никакой злоумышленник не сможет изменить его там, например чтобы обеспечить себе backdoor.

    А учитывая развитые средства для кодогенерации, которыми отличается лисп, можно вообще не иметь практически никакого кода — пусть он сам будет генерироваться по данным — лучше самурая, который не сражается, может быть только программист, который не пишет код… Хм, что-то я тут увлекся, вернемся к установке :)

    Итак, если у вас есть удаленный сервер, как у меня — вы можете ставить SWANK на него, а SLIME — на свою рабочую машину. Или
    поставить и то и другое и туда и туда — главное потом не перепутать. Устанавливаем SWANK:

    $ sbcl
    * (ql:quickload 'swank)
    


    … и SLIME

    $ wget http://common-lisp.net/project/slime/snapshots/slime-current.tgz
    $ tar xvzf slime-current.tgz
    $ cd slime-2011-01-06/
    


    Внимательно читаем README в этом каталоге и добавляем в свой
    ~/.emacs/init.el следующий код, следя за правильным указанием путей

    ;; SBCL
    (setq inferior-lisp-program "/opt/sbcl/bin/sbcl") ; your Lisp system
    (setq slime-lisp-implementations '((sbcl ("sbcl"))))
    (setq slime-startup-animation nil)
    ;; SLIME
    (add-to-list 'load-path "~/.emacs.d/slime") ;; Путь к slime
    (require 'slime)
    (setq slime-net-coding-system 'utf-8-unix)
    (slime-setup '(slime-fancy))
    (setq slime-enable-evaluate-in-emacs t)
    


    Ставим screen



    Поскольку мои сервера никогда не падают (ну да :) — я использую screen, чтобы держать вечно запущенную копию SBCL, хотя, насколько мне известно, существуют и более лучшие практики (о которых компетентные читатели, несомненно, напомнят в комментариях)

    Если он у вас еще не стоит — самое время поставить:

    $ apt-get install screen
    


    Запускаем sbcl в screen на сервере и стартуем swank-сервер на 4005 порту



    $ screen -S sbcl
    $ sbcl
    * (require 'asdf)
    * (asdf:oos 'asdf:load-op 'swank)
    * (setq swank:*use-dedicated-output-stream* nil)
    * (swank:create-server :coding-system "utf-8-unix" :dont-close t :port 4005)
    


    Подключаемся из Emacs-a запущенного на своей машине к лисп-образу на сервере



    В терминале прокидываем до хоста ssh-туннель

    ssh -2 -N -f -L 4005:localhost:4005 user@host.tld
    


    В Emacs-е подключаемся через этот туннель

    M-x slime-connect
    127.0.0.1
    4005
    


    Или, если у вас сервером является ваша домашняя машина — ничего прокидывать и поднимать SWANK не нужно — просто наберите в Emacs-e:

    M-x slime
    


    Запускаем web-server hunchentoot на 4242 порту



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

    Конфиг nginx для наших тестовых целей может быть очень простым:

    server {
       listen   80;
       server_name  localhost;
        location / {
          proxy_pass      http://localhost:4242;
          proxy_redirect  off;
       }
    }
    


    Нижеследующий код, за авторством archimag-a создает специальный класс, который позволяет при возникновении ошибки на удаленном сервере сразу же пробросить эту ошибку вместе со стек-трейсом вам в уютненький emacs, где вы сможете с ней разобраться как следует. Таким образом, если вы подключены к серверу, где работает ваш сайт — вы будете всегда осведомлены о возникающих ошибках в момент их появления, в отличии от ряда других языков, используемых в веб-программировании.

    Если же ошибок возникает слишком много, например на ошибочную странцицу постоянно заходят посетители — вы можете просто изменить значение *catch-errors-p* чтобы без помех разобраться из-за чего возникают уже пришедшие вам ошибки.

    (defparameter *catch-errors-p* nil)
    
    (defclass debuggable-acceptor (hunchentoot:acceptor) ())
    
    (defmethod hunchentoot:acceptor-request-dispatcher ((acceptor debuggable-acceptor))
      (if *catch-errors-p*
    	  (call-next-method)
    	  (let ((dispatcher (handler-bind ((error #'invoke-debugger))
    						  (call-next-method))))
    		(lambda (request)
    		  (handler-bind ((error #'invoke-debugger))
    			(funcall dispatcher request))))))
    
    (defun request-dispatcher (request)
        "Hello!")
    
    (defparameter *debuggable-acceptor* (make-instance 'debuggable-acceptor
                                                       :request-dispatcher 'request-dispatcher
                                                       :port 4242))
    
    (hunchentoot:start *debuggable-acceptor*)
    (setf hunchentoot:*handle-http-errors-p* nil)
    


    Функция request-dispatcher вызывается при каждом входящем запросе и в нашем случае просто возвращает «Hello!» — о ее расширении мы поговорим в следующей статье.

    Если вы дочитали до этого места надо поставить вам бутылку
    пива
    , да еще и подняли у себя тестовый сайт на лиспе — я вам
    завидую! Знакомство с лиспом подарило мне почти полтора года
    наслаждения этим удивительным языком — и у вас в этом отношении все
    впереди. Happy hacking!
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 53
    • +2
      метод с ошибкой не корректен. Т.к. надо разделять production и не production окружения. А когда разработка идет на production окружение сразу… За это, в приличных местах, кандилябром по лицу бьют!
      • +2
        Лисп немного другой :)
        Я согласен, что нужно разделять окружения, но метод с ошибкой совершенно ортогонален. NASA «Deep Space 1» был вполне себе production, но и в production бывают ошибки, требующие исправления на живой системе. В этом отношении подход лиспа в том, чтобы не превращать production в застывший монолит, расчитывая что он будет более или меннее совершенным, а оставлять его пластичным и чуточку сырым, чтобы при случае можно было внести изменения. Впрочем, я еще не настоящий джедай и возможно чего-то не понимаю :)
        • –2
          Есть два способа разработки софта. Я предлагаю разработку коробочного продукта, а не проекта с поддержкой, вот и все.
          • +5
            Ты будешь учить Катапа, что из себя представляет лисп??? :D
            • +3
              Я могу, да — наглости хватит :)))
              В глубокой древности мудрец Уолл сформулировал три добродетели, равно необходимых и программисту, и магу: лень, нетерпение и наглость — и функциональное программирование способствует достижению каждой из них.
            • 0
              помогите: в какой части этой статьи было описано создание веб-сайта? Или будет продолжение?
              • 0
                Последние четыре формы в последнем куске кода — Hello World.
                • +1
                  Спасибо. Я не заметил Hello world — а так бы подумал что настройки сервера.
                • 0
                  Продолжение будет. Здесь описана установка всего необходимого, чтобы запустить Hello World.
                  • +1
                    Написано же, «за 9 шагов». Это был первый :)
              • +2
                отличная статья, еще раз доказывает то, что не главное на чем написано, а как написано!
                • +6
                  Не увидел ни одного из 9 общеанных шагов. «Запускаем имакс, настраиваем SLIME и SWANK», конечно, относятся к веб-сайту, но с таким же успехом можно было написать «покупаем ПеКа для учебы и ставим на нем линукс» — вроде бы тоже имеет отношение, но как-то слабо раскрывает тему.
                  • –16
                    слишком много сраных скобок
                    • НЛО прилетело и опубликовало эту надпись здесь
                      • +1
                        вы путаете его с clean или попсовым haskell
                        • 0
                          Что значит попсовым? То, что им в последнее время заинтересовались — это положительный факт. Вашей логикой С++ — вообще попсовейшее УГ.
                        • +2
                          Ничего подобного :) Понять Лисп даже проще, чем разобраться в дебрях синтаксиса С++. Всё очень логично и незапутано.
                        • +2
                          Лисп смеётся мне в лицо!

                          Но на самом деле, имхо, код достаточно приятен. И эти смайлики только поднимают настроение.
                          • +1
                            Как будто религия запрещает скобки развернуть

                            (defmethod hunchentoot:acceptor-request-dispatcher ((acceptor debuggable-acceptor))
                                (if *catch-errors-p*
                                    (call-next-method)
                            		
                                    (let 
                                        (
                                            (dispatcher 
                                                (handler-bind 
                                                    ((error #'invoke-debugger))
                                                    (call-next-method)
                                                )
                                            )
                                        )
                            			
                                        (lambda (request)
                                            (handler-bind ((error #'invoke-debugger))
                                                (funcall dispatcher request)
                                            )
                                        )
                                    )
                                )
                            )
                            
                            • 0
                              как-будто, это уменьшит количество скобок
                            • –3
                              ЛИСП всегда иронично называли кучей дурацких скобок, а уж когда ты пишешь, не «трех строчный код», а программы, это превращается в целый ужас — вспомнить где что. Вспоминая карточную игру с «аи», которую заставляли писать в качестве ДЗ на нем, аж слезятся глаза.
                              fat0troll, о чем с тобой говорить, если ты думаешь что он чисто академический?
                              • +1
                                «На любом языке можно писать как на Фортране» Ⓒ
                                В следующих постах будет много примеров кода — как понятных, так и запутанных и все их можно будет обсудить как с точки зрения читабельности, так с точки зрения архитектуры.

                                Мне кажется, что я смогу показать, что лисп — читабельный, понятный и выразительный язык
                                • 0
                                  Я так смотрю, вы и sendmail.cf без помощи m4 не писали…
                              • +1
                                «В данный момент я разворачиваю его вместе с вами… и в конце статьи вы сможете убедиться в том, что по крайней мере у меня все заработало.»
                              • +4
                                Ошибка многих статей про Common Lisp — это сваливать в одну кучу ASDF и ASDF-Install/Quicklisp/clbuild/…. Надо понимать, что это инструменты разного класса:
                                • ASDF загружает код из файлов в «живой» образ лиспа. Он не является пакетным менеджером.
                                • ASDF-Install/Quicklisp/clbuild и прочие используются для доставки кода на жесткий диск, после чего ASDF загружает код с жесткого диска.
                                • +4
                                  <? print «Hello»; ?>
                                  :))
                                  • 0
                                    Нет. Пока только эквивалент установки php, apache, настройки виртуальных хостов, подключению mod_rewrite и тому подобного. Завтра будет. Лучше :)
                                  • 0
                                    Как то не вяжется описанный метод установки с аватаркой вначале поста. Этот человек не будет устанавливать лисп как трус: сначала с пакетного менеджера, а потом компилить. Я вообще не уверен что его пакетный менеджер сразу бинарные пакеты устанавливает.
                                    • +1
                                      Конечно можно со старта поразить всех умищем, но кто повторит все эти шаги тогда? А мне хотелось бы получить людей способных поставить себе на сервере все что нужно чтобы продолжить погружение в мир лиспа
                                    • 0
                                      Позвольте полюбопытствовать: чем обусловлен выбор Common Lisp? Я имею ввиду именно этот диалект.
                                      • 0
                                        • Распространенность
                                        • Хорошая поддержка юникода — было важным доводом в сравнении с php, например :)
                                        • Cтабильность, в сочетании с частыми минорными релизами
                                        • Кроссплатформенность (в том числе и в среде разработки — Емакс есть везде) вместе с быстродействием моей реализации — SBCL компилирует в машинный код
                                        • Значительное количество библиотек (см. CLiki)
                                        • Обширное лисп-комьюнити, в том числе множество квалифицированных рускоязычных пользователей(archimag dmitry_vk swizard.livejournal.com и другие)
                                        • Множество вменяемых руководств (Practical Common Lisp, OnLisp, Мир Лиспа)
                                        • Мощность макросов
                                        • Развитая объектная подсистема (CLOS)
                                        • Репутация языка
                                        • 0
                                          Я сейчас на перепутье какой из трех диалектов лиспа учить — Scheme, Common Lisp или Clojure. Scheme мне почему-то очень нравится + сейчас смотрю SICP где он используется.
                                          Clojure сравнительно молодой и динамино развивающийся, он есть для JVM и CLR.
                                          Common Lisp очень старый и умеет все, но чем-то он мне не нравится (cкорее всего словом defun).
                                          • 0
                                            В таком случае при прочих равных стоит брать Common Lisp как наиболее зрелый и содержащий большее количество интересных идей.
                                            • 0
                                              Как раз таки с идеями в CL напряг. Посмотрите на Typed Racket и syntax-parse — CL до этого как до Китая.
                                              • 0
                                                Хочу увидеть пост об этом
                                                • 0
                                                  Хорошая идея, спасибо.
                                            • 0
                                              Аналогично. Плюс лично мне дико не нравятся
                                              -убогие макросы
                                              -два неймспейса (привет, функол)
                                              -стрёмные названия функций
                                              В свою очередь, мне сильно нравится тамошний for. Но его можно переписать на какой угодно макросистеме при желании.
                                              Только тогда уж не Scheme, а Racket. Для него тоже есть веб-фреймворки, причём один из них «из коробки».
                                              • 0
                                                Собственно у меня Racket и стоит, хотя от изменения названия в нем меньше Scheme не стало (при #lang racket он все равно поддерживает R6RS).
                                                • 0
                                                  В убогости макросов CL их сила. В сложности макросов Racket их слабость.

                                                  Два неймспейса — это спорный момент. Но практика показала что проблемы LISP-1 принципиальные, а LISP-2 — эстетические, как и название defun.

                                                  Идей действительно интересных много, но, похоже, кроме PLT они никому не нужны… к сожалению.
                                                  • 0
                                                    >В убогости макросов CL их сила. В сложности макросов Racket их слабость.
                                                    1) Макросы Racket вполне себе простые. Что вообще может быть проще паттерн-матчинга?
                                                    2) Само утверждение напоминает приснопамятное «Война — это мир. Свобода — это рабство. Незнание — сила».
                                                    >проблемы LISP-1 принципиальные
                                                    Например?
                                                    • 0
                                                      > Что вообще может быть проще паттерн-матчинга?

                                                      Жонглирование символьными выражениями. В нетривиальных случаях оказывается куда проще.

                                                      > Например?

                                                      Вы правы, недостатки и там и там чисто эстетические.
                                                      • 0
                                                        «жонглирование символьными выражениями» ещё более нетривиально. Попробуйте описать рекурсивный макрос для какого-нибудь факториала без gensym'ов и прочих хаков. В Racket'е о таких вещах задумываться не нужно — автоматическая гигиена всё сделает сама. Плюс ещё много нужных и приятных вещей: www.cs.indiana.edu/~chaynes/danfest/dyb.pdf
                                                        • 0
                                                          Автоматическая гигиена Racket — точно такой же хак как и gensym.
                                                          • 0
                                                            В одном случае это надо держать в уме, в другом — нет. Плюс к тому, система макросов Racket умеет в точности то же самое, что и CL (в либе даже defmacro есть для любителей), но умеет и многое другое — потому как оперирует не голыми s-expr, а s-expr с дополнительной лексической информацией. Именно поэтому макросистема CL является строгим подмножеством макросистемы Racket.
                                          • 0
                                            а почему не используете nohup?
                                            • 0
                                              Можно поднять несколько сессий screen и удобно переключаться между ними. В принципе — вопрос привычки, можно и nohup
                                            • 0
                                              У коллег ровно один аргумент против CL — отсутствие бесплатных хостингов. Пожалуйста, скажите что я просто не умею их искать.
                                              • 0
                                                Мы же серьезные, взрослые люди или стремимся произвести такое впечатление :) При развитии проекта в любом случае потребуется хостинг и лучше сразу начинать с хорошего. Можно поспрашивать у друзей, мне например catap предлагал сравнительно дешевый хостинг под Common Lisp. Никто не запрещает кооперироваться. Ну а если хочется попробовать — все можно делать на своей машине.
                                              • +1
                                                * (ql:quckload 'swank)
                                                опечатка, пропущена i
                                              • 0
                                                И дабы не захламлять систему, я бы предложил собрать свежий sbcl самому, благо uupdate (лежит в пакете devscripts) существенно облегчает этот процесс:
                                                $ apt-get source sbcl
                                                $ sudo aptitude build-dep sbcl #тут много всего, в т.ч. и texlive
                                                $ wget http://downloads.sourceforge.net/project/sbcl/sbcl/1.0.45/sbcl-1.0.45-source.tar.bz2
                                                $ cd sbcl-1.0.40.0/
                                                $ uupdate -v 1.0.45 ../sbcl-1.0.45-source.tar.bz2
                                                $ cd ../sbcl-1.0.45
                                                $ debuild -b -us -uc
                                                #и устанавливаем полученный пакет
                                                $ sudo dpkg -i ../sbcl_1.0.45-0ubuntu1_amd64.deb #скорее всего имя будет отличаться
                                                ну вот как-то так.
                                                • 0
                                                  Начиная с hunchentoot 1.2.0 HUNCHENTOOT:ACCEPTOR-REQUEST-DISPATCHER более нету , также нету поля hunchentoot:acceptor-dispatch-request. Вместо них можно использовать
                                                  (defmethod hunchentoot:acceptor-dispatch-request ((acceptor debuggable-acceptor) request) ...)
                                                  Пожалуйста, исправьте.

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