37

我之前在 Clojure 中编写过一个小型 Swing 应用程序,现在我想创建一个 Ajax 风格的 Web 应用程序。Compojure 现在看起来是最好的选择,所以这就是我要尝试的。

我想要一个真正的小编辑/尝试反馈循环,所以我不想在我做的每一个小改动后重新启动网络服务器。

实现这一目标的最佳方法是什么?默认情况下,我的 Compojure 设置(带有 ant deps/ant 和 Jetty 的标准东西)似乎不会重新加载我所做的任何更改。我必须使用 run-server 重新启动才能看到更改。由于 Java 传统和系统启动方式等原因。这可能是完全正常的,也是我从命令行启动系统时应该采用的方式。

尽管如此,必须有一种方法可以在服务器运行时动态重新加载内容。我应该使用 REPL 的 Compojure 来实现我的目标吗?如果我应该,我如何在那里重新加载我的东西?

4

8 回答 8

38

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

你想要的主要有两件事:

  1. 控制权应返回到 REPL,以便您可以继续与服务器交互。这是通过添加 {:join? false} 启动 Jetty 服务器时的选项。
  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 函数意味着应用程序也可以打包为 uberjar,以便从 REPL 外部启动。(REPL 之外还有一个世界?好吧,反正有人要求了……)

于 2011-01-24T04:22:43.140 回答
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)) 
... 

您还可以将 run-server 命令放在另一个 clojure 文件中。但是,您不想将它与要重新加载的内容放在同一个文件中。

现在对 demo.clj 进行一些更改。在 REPL 类型中:

user=> (use 'demo :reload) 
nil 

您的更改现在应该显示在http://localhost:8080上

于 2009-11-03T11:22:11.037 回答
14

我想添加一个答案,因为自最新答案以来情况发生了一些变化,我自己花了一些时间寻找这个。

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

  2. 创建项目

    lein new compojure compojure-test 
    
  3. 编辑 project.clj 的 ring 部分

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

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

于 2013-01-23T04:06:03.707 回答
5

跟进 Timothy 与Jim Downing 设置的链接,我最近发布了对该基线的一个重要补充,我发现它对于在开发过程中启用 compojure 应用程序的自动重新部署是必要的。

于 2010-01-08T13:39:54.420 回答
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。

于 2009-11-03T15:53:05.343 回答
3

这高度依赖于配置,但对我有用,我认为你可以调整它:

  1. 将 compojure.jar 和 compojure/deps 目录下的 jars 放在你的类路径中。我使用 clojure-contrib/launchers/bash/clj-env-dir 来执行此操作,您需要做的就是在 CLOJURE_EXT 中设置目录,它会找到罐子。CLOJURE_EXT 以冒号分隔的目录路径列表,其顶级内容是(直接或作为符号链接)jar 文件和/或路径将在 Clojure 的类路径中的目录。

  2. 启动 clojure REPL

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

  4. 检查本地主机:8080

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

  6. 检查本地主机:8080

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

于 2009-11-03T11:01:13.787 回答
1

我想跟进 mtnygard 的回答并发布使给定功能正常工作的完整 project.clj 文件和 core.clj 文件。做了一些修改,它更准系统

预设置命令

lein new app test-web
cd test-web
mkdir resources

项目.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}})

核心.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 ...)的变化

这对于让码头服务器正常工作至关重要

于 2014-02-09T18:50:16.663 回答
0

Compojure 在内部使用ring(由同一作者),ring web 服务器选项允许自动重新加载。所以有两种选择:

lein ring server
lein ring server-headless
lein ring server 4000
lein ring server-headless 4000

注意 :

您需要在 project.clj 文件中有一行如下所示:
:ring {:handler your.app/handler}

于 2015-03-14T20:28:31.657 回答