我最近通过为 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 库。
如何构建一个独立的可执行文件
另请参阅 https://github.com/CodyReichert/awesome-cl#interfaces-to-other-package-managers 以了解与 Homebrew 和 Debian 软件包的绑定。
带 SBCL
如何构建(独立的)可执行文件是特定于实现的(请参阅
在 Buildapp 和 Rowsell 下方)。正如所说,使用 SBCL
its documentation,
这是一个问题:
(sb-ext:save-lisp-and-die #P"path/name-of-executable" :toplevel #'my-app:main-function :executable t)
sb-ext 是运行外部进程的 SBCL 扩展。查看其他
SBCL extensions
(其中许多在其他库中实现可移植)。
:executable t 告诉构建可执行文件而不是
图片。我们可以构建一个图像来保存我们当前的状态
Lisp 图像,稍后再使用它。特别有用,如果
我们做了很多计算密集型的工作。
如果你尝试在 Slime 中运行它,你会收到关于线程运行的错误:
在运行多个线程时无法保存核心。
从一个简单的 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 command,
从.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 服务器,并且能够在
命令行:
$ ./my-web-app
Hunchentoot server is started.
Listening on localhost:9003.
请注意,这运行的是生产网络服务器,而不是开发网络服务器,
所以我们可以立即在我们的 VPS 上运行二进制文件并从
外面。
我们有一件事情要处理,那就是找到并放置线程
在前台运行的 Web 服务器。在我们的main 函数中,我们
可以这样做:
(defun main ()
(start-app :port 9003) ;; our start-app, for example clack:clack-up
;; let the webserver run.
;; warning: hardcoded "hunchentoot".
(handler-case (bt:join-thread (find-if (lambda (th)
(search "hunchentoot" (bt:thread-name th)))
(bt:all-threads)))
;; Catch a user's C-c
(#+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 获取参数列表。要真正解析它们,有库。
部署
简单明了的可执行文件。该网络应用程序从外部立即可见。
在 Heroku 上
见this buildpack。
守护进程、崩溃时重启、处理日志
了解如何在您的系统上执行此操作。
现在大多数 GNU/Linux 发行版都带有 Systemd。
示例search 结果:
就像写一个配置文件一样简单:
# /etc/systemd/system/my-app.service
[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 可以处理 logging(我们写入 stdout 或 stderr,它会写入日志):
journalctl -f -u my-app.service
它会处理崩溃并重启应用:
Restart=always
它可以重启后启动应用:
[Install]
WantedBy=basic.target
启用它:
sudo systemctl enable my-app.service
调试 SBCL 错误:ensure_space: failed to allocate n bytes
如果您的服务器上的 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/
它定义了一个永远打印的简单函数:
;; a little common lisp swank demo
;; while this program is running, you can connect to it from another terminal or machine
;; and change the definition of doprint to print something else out!
;; (ql:quickload :swank)
;; (ql:quickload :bordeaux-threads)
(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 端口(swanks 接受来自
本地主机)。
我们使用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 在
服务器,因此新代码存在但未运行。它连接到
本地 swank 服务器,加载新代码,停止和启动应用程序
行。
持续集成、持续交付可执行文件、Docker
见https://lispcookbook.github.io/cl-cookbook/testing.html#continuous-integration