Hunchentoot(easy-routes)多项目路由异常问题排查求助
我有两个独立的ASDF系统/defprojects,均使用Hunchentoot和easy-routes。项目1端口为8000,文档根目录为/Users/vince/quicklisp/local-projects/sending-app/web/,对应页面admin.html;项目2端口为9000,文档根目录为/Users/vince/quicklisp/local-projects/sales-web/web/,同样包含admin.html页面。二者路由名称与路径均为/admin,但分属不同项目、不同端口。然而启动项目2的start-server2后,却激活了项目1的路由,修改项目1的admin.html会在9000端口生效,请问可能是什么问题?
项目1代码
(in-package :project-one) ;; ==== Server Setup ==== ;; (defvar *server* nil) (defvar *document* "/Users/vince/quicklisp/local-projects/sending-app/web/") (defun start-server2 () (start (setf *server* (make-instance 'easy-routes-acceptor :document-root *document* :port 8000)))) (defun stop-server () (when *server* (stop *server*) (start-server2))) ;; ==== Djula Setup ==== ;; (djula:add-template-directory *document*) (defparameter *admin.html* (compile-template* "admin.html")) (defroute admin ("/admin" :method :get) () (render-template* *admin.html* nil :title "Project 2"))
项目2代码
(in-package :project-two) ;; ==== Server Setup ==== ;; (defvar *server* nil) (defvar *document* "/Users/vince/quicklisp/local-projects/sales-web/web/") (defun start-server2 () (start (setf *server* (make-instance 'easy-routes-acceptor :document-root *document* :port 9000)))) (defun stop-server () (when *server* (stop *server*) (start-server2))) ;; ==== Djula Setup ==== ;; (djula:add-template-directory *document*) (defparameter *admin.html* (compile-template* "admin.html")) (defroute admin ("/admin" :method :get) () (render-template* *admin.html* nil :title "Project 2"))
核心问题1:easy-routes的全局路由注册表
easy-routes默认将所有defroute定义的路由注册到全局路由表,不区分包。启动第二个服务器时,它会加载全局中所有已注册的路由(包括项目1的/admin),而路由匹配按注册顺序优先级执行,先注册的项目1路由会被优先命中,导致9000端口也走项目1的逻辑。
核心问题2:Djula的全局模板管理
Djula的add-template-directory是将目录添加到全局模板搜索路径,两个项目的目录都加入后,搜索admin.html时会按添加顺序查找。如果项目1的目录先被添加,编译模板时会优先使用项目1的文件;再加上Djula默认缓存编译后的模板,即使后续添加了项目2的目录,也不会重新搜索,最终导致修改项目1的模板会在9000端口生效。
解决步骤
给easy-routes配置独立路由表
每个项目创建专属路由表实例,在定义服务器和路由时指定该表,避免共用全局表:
以项目1为例修改:(in-package :project-one) ;; 创建项目专属路由表 (defvar *project-one-routes* (make-instance 'easy-routes:route-table)) ;; 启动服务器时绑定专属路由表 (defun start-server2 () (start (setf *server* (make-instance 'easy-routes-acceptor :document-root *document* :port 8000 :route-table *project-one-routes*)))) ;; 定义路由时指定专属路由表 (defroute admin ("/admin" :method :get :route-table *project-one-routes*) () (render-template* *admin.html* nil :title "Project 1"))项目2同理,创建
*project-two-routes*并在服务器、路由定义中绑定。给Djula使用独立模板环境
每个项目创建专属模板环境,独立管理模板目录与缓存,避免全局污染:
项目1修改:(in-package :project-one) ;; 创建项目专属模板环境 (defvar *project-one-templates* (djula:make-template-environment)) ;; 给专属环境添加模板目录 (djula:add-template-directory *document* :environment *project-one-templates*) ;; 编译模板时指定专属环境 (defparameter *admin.html* (djula:compile-template* "admin.html" :environment *project-one-templates*)) ;; 渲染时绑定专属环境 (defroute admin ("/admin" :method :get :route-table *project-one-routes*) () (djula:render-template* *admin.html* nil :title "Project 1" :environment *project-one-templates*))项目2同理,创建
*project-two-templates*并在所有Djula操作中指定。修复服务器停止逻辑
两个项目的stop-server函数存在错误:停止服务器后立刻重启,会导致状态混乱。应改为单纯停止并清空变量:(defun stop-server () (when *server* (stop *server*) (setf *server* nil)))
内容的提问来源于stack exchange,提问作者Vinn

