Pull to refresh

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

Reading time 7 min
Views 12K

Введение





Эта вводная статья предназначена для желающих попробовать применить 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!
Tags:
Hubs:
Total votes 76: ↑66 and ↓10 +56
Comments 53
Comments Comments 53

Articles