You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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端口生效。

解决步骤

  1. 给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*并在服务器、路由定义中绑定。

  2. 给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操作中指定。

  3. 修复服务器停止逻辑
    两个项目的stop-server函数存在错误:停止服务器后立刻重启,会导致状态混乱。应改为单纯停止并清空变量:

    (defun stop-server ()
      (when *server*
        (stop *server*)
        (setf *server* nil)))
    

内容的提问来源于stack exchange,提问作者Vinn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 15:03:14