无需重启 Web 服务器的 Compojure 开发
Posted
技术标签:
【中文标题】无需重启 Web 服务器的 Compojure 开发【英文标题】:Compojure development without web server restarts 【发布时间】:2010-12-12 13:16:24 【问题描述】:我之前在 Clojure 中编写过一个小型 Swing 应用程序,现在我想创建一个 Ajax 风格的 Web 应用程序。 Compojure 看起来是目前最好的选择,所以这就是我要尝试的。
我想要一个真正的小编辑/尝试反馈循环,所以我不希望在我做的每一个小改动后重新启动网络服务器。
完成此任务的最佳方法是什么?默认情况下,我的 Compojure 设置(带有 ant deps/ant 和 Jetty 的标准东西)似乎不会重新加载我所做的任何更改。我必须使用 run-server 重新启动才能看到更改。由于 Java 传统和系统启动方式等原因。这可能是完全正常的,也是我从命令行启动系统时应该采用的方式。
不过,必须有一种方法可以在服务器运行时动态地重新加载内容。我应该使用 REPL 的 Compojure 来实现我的目标吗?如果我应该,我如何在那里重新加载我的东西?
【问题讨论】:
【参考方案1】:这是一个相当古老的问题,最近发生了一些变化,使这个问题变得更加容易。
你想要的主要有两件事:
-
控制权应返回到 REPL,以便您可以继续与服务器交互。这是通过添加 :join? false 到启动 Jetty 服务器时的选项。
您希望在文件更改时自动获取某些命名空间中的更改。这可以通过 Ring 的“wrap-reload”中间件来完成。
玩具应用程序如下所示:
(ns demo.core
(:use webui.nav
[clojure.java.io]
[compojure core response]
[ring.adapter.jetty :only [run-jetty]]
[ring.util.response]
[ring.middleware file file-info stacktrace reload])
(:require [compojure.route :as route] view)
(:gen-class))
; Some stuff using Fleet omitted.
(defroutes main-routes
(GET "/" [] (view/layout :body (index-page))
(route/not-found (file "public/404.html"))
)
(defn app
[]
(-> main-routes
(wrap-reload '(demo.core view))
(wrap-file "public")
(wrap-file-info)
(wrap-stacktrace)))
(defn start-server
[]
(run-jetty (app) :port 8080 :join? false))
(defn -main [& args]
(start-server))
wrap-reload 函数使用检测列出的命名空间中的更改的函数来装饰您的应用程序路由。在处理请求时,如果这些命名空间在磁盘上发生了变化,它们会在进一步的请求处理之前重新加载。 (我的“视图”命名空间是由 Fleet 动态创建的,所以每当模板发生变化时,它也会自动重新加载。)
我添加了其他一些我发现始终有用的中间件。 wrap-file 处理静态资产。 wrap-file-info 设置这些静态资产的 MIME 类型。 wrap-stacktrace 有助于调试。
在 REPL 中,您可以通过使用命名空间并直接调用 start-server 来启动此应用程序。 :gen-class 关键字和 -main 函数意味着应用程序也可以打包为 uberjar,以便从 REPL 外部启动。 (REPL 之外还有一个世界吗?好吧,反正有些人要求它……)
【讨论】:
我可以建议每个示例都提到所需的 ring 等版本。谢谢!【参考方案2】:这是我从 Compojure Google Group 中的James Reeves 得到的答案(答案在他的许可下在这里):
您可以在 Clojure 中使用 :reload 键重新加载名称空间 或需要命令。例如,假设您有一个包含您的路线的文件“demo.clj”:
(ns demo
(:use compojure))
(defroutes demo-routes
(GET "/"
"Hello World")
(ANY "*"
[404 "Page not found"]))
在 REPL 中,您可以使用此文件并启动服务器:
user=> (use 'demo)
nil
user=> (use 'compojure)
nil
user=> (run-server :port 8080 "/*" (servlet demo-routes))
...
您也可以将 run-server 命令放在另一个 clojure 文件中。 但是,您不想将它与要重新加载的内容放在同一个文件中。
现在对 demo.clj 进行一些更改。在 REPL 类型处:
user=> (use 'demo :reload)
nil
您的更改现在应该显示在http://localhost:8080
【讨论】:
【参考方案3】:我想添加一个答案,因为自最新答案以来情况发生了一些变化,我自己花了一些时间寻找这个。
安装leiningen(只需按照那里的说明操作)
创建项目
lein new compojure compojure-test
编辑 project.clj 的 ring 部分
:ring :handler compojure-test.handler/app
:auto-reload? true
:auto-refresh? true
在您想要的任何端口上启动服务器
lein ring server-headless 8080
检查服务器是否在您的浏览器中运行,默认的基本路由应该只是说“Hello world”。接下来,去修改你的处理程序(它在 src/project_name 中)。更改 hello world 文本,保存文件并在浏览器中重新加载页面。它应该反映新的文本。
【讨论】:
【参考方案4】:根据 Timothy 指向 Jim Downing's setup 的链接,我最近发布了对该基线的一个重要补充,我发现它对enable automatic redeployment of compojure apps during development 是必要的。
【讨论】:
好帖子!它与 NetBeans 的集成情况如何?我的日常工作是 eclipse 用户,但我正在寻找一个更好的平台来扩展到 Clojure。 NetBeans 对 maven 有 excellent 支持,而 enclojure 插件是目前最好的 clojure 环境,IMO。 Emacs/swank/slime 用户肯定会不同意,但我怀疑 emacs/vim/IDE 阵营之间无论如何都没有缓和的机会。【参考方案5】:我有一个看起来像这样的 shell 脚本:
#!/bin/sh
CLASSPATH=/home/me/install/compojure/compojure.jar
CLASSPATH=$CLASSPATH:/home/me/clojure/clojure.jar
CLASSPATH=$CLASSPATH:/home/me/clojure-contrib/clojure-contrib.jar
CLASSPATH=$CLASSPATH:/home/me/elisp/clojure/swank-clojure
for f in /home/me/install/compojure/deps/*.jar; do
CLASSPATH=$CLASSPATH:$f
done
java -server -cp $CLASSPATH clojure.lang.Repl /home/me/code/web/web.clj
web.clj 看起来像这样
(use '[swank.swank])
(swank.swank/ignore-protocol-version "2009-03-09")
(start-server ".slime-socket" :port 4005 :encoding "utf-8")
每当我想更新服务器时,我都会创建一个从本地机器到远程机器的 ssh 隧道。
Enclojure 和 Emacs(运行 SLIME+swank-clojure)可以连接到远程 REPL。
【讨论】:
我以前读过关于连接到远程 REPL,但你能告诉我如何在 emacs 中做到这一点吗?我的 SLIME 知识目前仅限于 M-x slime -> 开始破解... 使用 M-x slime-connect 连接到可能远程运行的 swank 服务器。您可以按照上面“web.clj”中所述的方式启动一个 swank 服务器 新的 Leiningen 构建工具 (github.com/technomancy/leiningen) 自动执行此操作。您可以在命令行中输入“lein swank”,然后 M-x slime-connect(或“lein repl”以获取命令行 REPL。)【参考方案6】:这高度依赖于配置,但对我有用,我认为您可以调整它:
将 compojure.jar 和 compojure/deps 目录下的 jar 放在您的类路径中。我使用 clojure-contrib/launchers/bash/clj-env-dir 来执行此操作,您需要做的就是在 CLOJURE_EXT 中设置目录,它会找到罐子。 CLOJURE_EXT 以冒号分隔的***目录的路径列表 内容是(直接或作为符号链接)jar 路径将在 Clojure 中的文件和/或目录 类路径。
启动 clojure REPL
从 compojure 根目录粘贴 hello.clj 示例
检查 localhost:8080
重新定义欢迎程序 (defroutes 迎宾员 (得到 ”/” (html [:h1 "再见世界"])))
检查 localhost:8080
还有一些方法可以将 REPL 附加到现有进程,或者您可以将套接字 REPL 嵌入到您的服务器中,或者您甚至可以定义一个 POST 调用,该调用将在运行中进行评估,以允许您从浏览器重新定义函数本身!有很多方法可以解决这个问题。
【讨论】:
【参考方案7】:我想跟进 mtnygard 的回答并发布使给定功能正常工作的完整 project.clj 文件和 core.clj 文件。做了一些修改,它更准系统
预设置命令
lein new app test-web
cd test-web
mkdir resources
project.clj
(defproject test-web "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license :name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"
:dependencies [[org.clojure/clojure "1.5.1"]
[compojure "1.1.6"]
[ring "1.2.1"]]
:main ^:skip-aot test-web.core
:target-path "target/%s"
:profiles :uberjar :aot :all)
core.clj
(ns test-web.core
(:use
[clojure.java.io]
[compojure core response]
[ring.adapter.jetty :only [run-jetty]]
[ring.util.response]
[ring.middleware file file-info stacktrace reload])
(:require [compojure.route :as route])
(:gen-class))
(defroutes main-routes
(GET "/" [] "Hello World!!")
(GET "/hello" [] (hello))
(route/not-found "NOT FOUND"))
(def app
(-> main-routes
(wrap-reload '(test-web.core))
(wrap-file "resources")
(wrap-file-info)
(wrap-stacktrace)))
(defn hello []
(str "Hello World!"))
(defn start-server
[]
(run-jetty #'app :port 8081 :join? false))
(defn -main [& args]
(start-server))
注意从(defn app ...)到(def app ...)的变化
这对于让码头服务器正常工作至关重要
【讨论】:
【参考方案8】:Compojure 在内部使用ring(同一作者),ring web server options 允许自动重新加载。所以有两种选择:
lein ring server
lein ring server-headless
lein ring server 4000
lein ring server-headless 4000
请注意:
您需要在 project.clj 文件中包含如下所示的一行: :ring :handler your.app/handler
【讨论】:
以上是关于无需重启 Web 服务器的 Compojure 开发的主要内容,如果未能解决你的问题,请参考以下文章
在 Clojure/Compojure 中转义/清理用户输入