使用Compojure进行开发时无需重新启动Web服务器

37

我之前用Clojure写过一个小的Swing应用程序,现在我想创建一个Ajax风格的Web应用程序。目前看来,Compojure是最好的选择,所以我将尝试使用它。

我想要一个真正微小的编辑/尝试反馈循环,所以我希望不必在每次做出小改变后重新启动Web服务器。

怎样才能最好地实现这一点?默认情况下,我的Compojure设置(使用ant deps/ant with Jetty的标准设置)似乎不会重新加载我所做的任何更改。我必须通过run-server来查看更改。由于Java的继承和系统启动方式等原因,这可能是从命令行启动系统时应该采取的正常方式。

尽管如此,在服务器运行时动态重新加载内容肯定有办法。我应该使用REPL操作Compojure以实现我的目标吗?如果是这样,我该如何在其中重新加载我的内容?

8个回答

38

这是一个相当古老的问题,最近有一些变化使得这个问题变得更加容易。

你需要两件主要的事情:

  1. 控制应该返回到REPL,以便您可以继续与服务器交互。这可以通过在启动Jetty服务器时将 {:join? false} 添加到选项中来实现。
  2. 当文件发生更改时,您希望自动获取某些命名空间中的更改。这可以使用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函数意味着该应用程序也可以打包为超级jar文件,以便从REPL外部启动。(REPL外面还有一个世界?好吧,有些人确实要求这样做...)


2
我可以建议在每个示例中提到所需的ring等版本。谢谢! - Johnny

22

我得到了来自Compojure Google Group的James Reeves 的一个答案(在他的许可下,这里有答案):

您可以使用use或require命令上的:reload键重新加载Clojure中的名称空间。例如,假设您有一个名为“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)) 
... 
你也可以将运行服务器的命令放在另一个Clojure文件中。但是,你不想把它放在与要重新加载的内容相同的文件中。
现在对demo.clj进行一些更改。在REPL中输入:
user=> (use 'demo :reload) 
nil 

你所做的更改现在应该显示在 http://localhost:8080 上。


14

由于最新回答和我自己花了一些时间寻找此问题,我希望添加一个答案。

  1. 安装Leiningen(只需按照那里的说明操作)

  2. 创建项目

    lein new compojure compojure-test 
    
  3. 编辑 project.clj 文件中的 ring 部分。

  4. :ring {:handler compojure-test.handler/app 
           :auto-reload? true
           :auto-refresh? true}
    
  5. 在任何你想要的端口上启动服务器

  6. lein ring server-headless 8080
    
  7. 检查服务器是否在您的浏览器中运行, 默认基础路由应该只显示“Hello world”。接下来,进入修改您的处理程序(它在src/project_name中)。更改hello world文本,保存文件并重新加载浏览器中的页面。它应该反映出新文本。


5

很棒的文章!它与NetBeans整合得如何?我在日常工作中使用Eclipse,但正在寻找一个更好的平台来尝试Clojure。 - acfoltzer
1
NetBeans对Maven的支持非常出色,而Enclojure插件则是目前最好的Clojure环境,我个人认为。Emacs/swank/slime用户肯定会有不同看法,但我怀疑在Emacs/vim/IDE阵营之间没有缓和的机会。 - cemerick

4

我有一个像这样的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 -> start hacking... - auramo
2
请使用M-x slime-connect连接到可能远程运行的swank服务器。您可以按照上面在“web.clj”中概述的步骤启动swank服务器。 - ordnungswidrig
2
新的 Leiningen 构建工具 (http://github.com/technomancy/leiningen) 可以自动化这个过程。你只需要在命令行中输入 "lein swank",然后 M-x slime-connect(或者 "lein repl" 获取一个命令行 REPL)。 - Paul Legato

3
这个方法高度依赖于配置,但对我有效,我认为你可以适应它:
  1. 将compojure.jar和compojure/deps目录下的jar放入您的类路径中。我使用clojure-contrib/launchers/bash/clj-env-dir来完成这个操作,您只需要在CLOJURE_EXT中设置目录,它就会找到这些jar。 CLOJURE_EXT:以冒号分隔的路径列表,其顶层内容为(直接或作为符号链接)jar文件和/或目录,其路径将位于Clojure的类路径中。

  2. 启动clojure REPL

  3. 从compojure根目录复制并粘贴hello.clj示例

  4. 检查localhost:8080

  5. 重新定义greeter (defroutes greeter (GET "/" (html [:h1 "Goodbye World"])))

  6. 检查localhost:8080

还有一些将REPL附加到现有进程的方法,或者您可以在服务器中嵌入套接字REPL,甚至可以定义一个POST调用,以便您可以从浏览器本身重新定义函数!有很多方法可以解决这个问题。

1
我希望您能跟进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 ...)的变化

这对于使Jetty服务器正常工作至关重要。


0

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}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接