最近,我通过构建自包含的Web应用程序可执行文件发现了一些东西,并在lisp-journey/web-dev(发布和部署部分)以及Common Lisp Cookbook/scripting#for-web-apps上写了一些内容。
这里复制了一些有趣的部分,每个资源上还有更多内容。欢迎编辑,主要感谢那些资源!
编辑 2019年7月:我在Cookbook上贡献了一个页面:https://lispcookbook.github.io/cl-cookbook/web.html
编辑:另请参阅提供专业CL支持的工具和平台列表:https://github.com/CodyReichert/awesome-cl#deployment
(编辑)如何将Web应用程序作为脚本运行
我下面解释了如何构建和运行可执行文件,但我们当然也可以将应用程序作为脚本运行。在一个Lisp文件中,比如run.lisp
,请确保:
- 加载项目的asd文件:
(load "my-project.asd")
- 加载它的依赖项:
(ql:quickload :my-project)
- 调用其主函数:
(my-project:start)
(假设start
是一个导出的符号,否则为::start
)。
这样做,应用程序就会启动并返回一个Lisp REPL。您可以与正在运行的应用程序交互。您可以在运行时更新它甚至安装新的Quicklisp库。
如何构建自包含可执行文件
有关绑定到Homebrew和Debian软件包的信息,请参见https://github.com/CodyReichert/awesome-cl#interfaces-to-other-package-managers。
使用SBCL
如何构建(自包含)可执行文件取决于具体实现(请参见下面的Buildapp和Rowsell)。根据其文档,使用SBCL可以轻松完成:
(sb-ext:save-lisp-and-die #P"path/name-of-executable" :toplevel #'my-app:main-function :executable t)
sb-ext
是一个SBCL扩展,用于运行外部进程。请参阅其他SBCL扩展(其中许多在其他库中实现可移植性)。
:executable t
告诉要构建可执行文件而不是镜像。我们可以构建一个镜像来保存当前Lisp镜像的状态,以后再回来继续使用它。如果我们做了很多计算密集型工作,这将非常有用。
如果您尝试在Slime中运行此命令,则会收到有关运行线程的错误:
Cannot save core with multiple threads running.
从简单的SBCL repl中运行该命令。
我假设您的项目具有Quicklisp依赖项。然后,您必须:
- 确保在Lisp启动时安装并加载Quicklisp(您已完成Quicklisp安装)
load
项目的.asd文件
- 安装依赖项
- 构建可执行文件。
这样就可以了:
(load "my-app.asd")
(ql:quickload :my-app)
(sb-ext:save-lisp-and-die #p"my-app-binary" :toplevel #'my-app:main :executable t)
通过命令行或Makefile,使用--load
和--eval
:
build:
sbcl --non-interactive \
--load my-app.asd \
--eval '(ql:quickload :my-app)' \
--eval "(sb-ext:save-lisp-and-die #p\"my-app\" :toplevel #my-app:main :executable t)"
使用ASDF
现在我们已经了解了基础知识,需要一种便携式的方法。自从版本3.1以来,ASDF就允许这样做。它引入了make
命令,可以从.asd文件中读取参数。将以下内容添加到你的.asd声明中:
:build-operation "program-op" ;; leave as is
:build-pathname "<binary-name>"
:entry-point "<my-system:main-function>"
并调用asdf:make :my-system
。
因此,在Makefile中:
LISP ?= sbcl
build:
$(LISP) --non-interactive \
--load my-app.asd \
--eval '(ql:quickload :my-app)' \
--eval '(asdf:make :my-system)'
使用Roswell或Buildapp
Roswell是一个实现管理器,还有ros build
命令,可以适用于许多实现。
我们也可以使用ros install my-app
在Roswell中安装我们的应用程序。请参阅其文档。
最后,我们提到Buildapp,它是一个经过测试并仍然广受欢迎的“为SBCL或CCL配置和保存可执行Common Lisp图像的应用程序”。
许多应用程序使用它(例如pgloader),它在Debian上可用:apt install buildapp
,但您现在不需要使用asdf:make或Roswell。
对于Web应用程序
我们可以类似地为我们的Web应用程序构建一个自包含的可执行文件。因此,它将包含一个Web服务器,并能够在命令行上运行:
$ ./my-web-app
Hunchentoot server is started.
Listening on localhost:9003.
请注意,这里运行的是生产Web服务器,而不是开发服务器,因此我们可以立即在VPS上运行二进制文件并从外部访问应用程序。
我们有一件事情要处理,那就是找到并将正在运行的Web服务器线程放在前台。在我们的
main
函数中,我们可以这样做:
(defun main ()
(start-app :port 9003)
(handler-case (bt:join-thread (find-if (lambda (th)
(search "hunchentoot" (bt:thread-name th)))
(bt:all-threads)))
(#+sbcl sb-sys:interactive-interrupt
#+ccl ccl:interrupt-signal-condition
#+clisp system::simple-interrupt-condition
#+ecl ext:interactive-interrupt
#+allegro excl:interrupt-signal
() (progn
(format *error-output* "Aborting.~&")
(clack:stop *server*)
(uiop:quit)))
(error (c) (format t "Woops, an unknown error occured:~&~a~&" c))))
我们使用了
bordeaux-threads
库(
(ql:quickload "bordeaux-threads")
, 别名
bt
)和
uiop
,它是ASDF的一部分,所以已经加载,为了以便携的方式退出(
uiop:quit
,带有可选的返回代码,而不是
sb-ext:quit
)。
解析命令行参数
请参阅食谱
here。TLDR; 使用
uiop:command-line-arguments
获取参数列表。要进行真正的解析,需要使用库。
部署
通过可执行文件很容易。Web应用程序立即对外可见。
在Heroku上
请参阅
this buildpack。
守护进程、崩溃时重新启动、处理日志
请查看如何在您的系统上执行此操作。
大多数GNU/Linux发行版现在都配备了Systemd。
示例
search结果:
只需要编写一个配置文件,就可以轻松完成:
[Unit]
Description=stupid simple example
[Service]
WorkingDirectory=/path/to/your/app
ExecStart=/usr/local/bin/sthg sthg
Type=simple
Restart=always
RestartSec=10
运行命令以启动它:
sudo systemctl start my-app.service
一个检查其状态的命令:
systemctl status my-app.service
而 Systemd 可以处理日志记录(我们将日志写入 stdout 或 stderr,它会写入日志):
journalctl -f -u my-app.service
它处理崩溃并重新启动应用程序:
Restart=always
它可以在重启后启动应用程序:
[Install]
WantedBy=basic.target
启用它:
sudo systemctl enable my-app.service
调试SBCL错误: ensure_space: 分配n字节失败
如果您在服务器上使用SBCL时遇到此错误:
mmap: wanted 1040384 bytes at 0x20000000, actually mapped at 0x715fa2145000
ensure_space: failed to allocate 1040384 bytes at 0x20000000
(hint: Try "ulimit -a"; maybe you should increase memory limits.)
然后禁用ASLR:
sudo bash -c "echo 0 > /proc/sys/kernel/randomize_va_space"
连接到远程Swank服务器
这里有一个小例子: http://cvberry.com/tech_writings/howtos/remotely_modifying_a_running_program_using_swank.html。
演示项目在这里: https://lisp-journey.gitlab.io/blog/i-realized-that-to-live-reload-my-web-app-is-easy-and-convenient/
它定义了一个简单的函数,可以无限循环打印:
(require :swank)
(require :bordeaux-threads)
(defparameter *counter* 0)
(defun dostuff ()
(format t "hello world ~a!~%" *counter*))
(defun runner ()
(bt:make-thread (lambda ()
(swank:create-server :port 4006)))
(format t "we are past go!~%")
(loop while t do
(sleep 5)
(dostuff)
(incf *counter*)
))
(runner)
在我们的服务器上,我们使用以下方式运行它:
sbcl --load demo.lisp
我们在开发机上进行端口转发:
ssh -L4006:127.0.0.1:4006 username@example.com
这将安全地转发位于example.com的服务器上端口4006到我们本地计算机的端口4006(Swank接受来自localhost的连接)。
我们可以使用M-x slime-connect
连接正在运行的Swank,输入端口4006。
我们可以编写新代码:
(defun dostuff ()
(format t "goodbye world ~a!~%" *counter*))
(setf *counter* 0)
例如,使用M-x slime-eval-region
等方式正常评估它。输出应该会更改。
CV Berry的页面上有更多指针。
热重载
使用Quickutil进行示例。请参阅lisp-journey上的注释。
它必须在服务器上运行(一个简单的fabfile命令可以通过ssh调用此命令)。事先,fab update
已在服务器上运行了git pull
,因此新代码存在但不在运行中。它连接到本地swank服务器,加载新代码,连续停止和启动应用程序。
持续集成、可执行文件的持续交付、Docker
请参阅https://lispcookbook.github.io/cl-cookbook/testing.html#continuous-integration
asdf:make :my-package
时,我本来期望看到的是asdf:make :my-system
,因为尽管系统和软件包通常被命名为相同的名称,但这并非总是如此,这可能会带来混淆。除此之外,回答很棒! - coredump(declaim
。break example和log4cl。请参见trace
、step
、defadvice
等,在David B. Lamkins的“Successful lisp”中有一个大章节。在Cookbook中开一个问题! - Ehvince