无需重启 Web 服务器的 Compojure 开发

发布于 2024-08-10 06:59:01 字数 419 浏览 4 评论 0原文

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

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

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

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

I've written a small Swing App before in Clojure and now I'd like to create an Ajax-style Web-App. Compojure looks like the best choice right now, so that's what I'm going to try out.

I'd like to have a real tiny edit/try feedback-loop, so I'd prefer not to restart the web server after each small change I do.

What's the best way to accomplish this? By default my Compojure setup (the standard stuff with ant deps/ant with Jetty) doesn't seem to reload any changes I do. I'll have to restart with run-server to see the changes. Because of the Java-heritage and the way the system is started etc. This is probably perfectly normal and the way it should be when I start the system from command-line.

Still, there must be a way to reload stuff dynamically while the server is running. Should I use Compojure from REPL to accomplish my goal? If I should, how do I reload my stuff there?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

肤浅与狂妄 2024-08-17 06:59:01

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

您需要做两件主要的事情:

  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-info 设置这些静态资源的 MIME 类型。 wrap-stacktrace 有助于调试。

在 REPL 中,您可以使用命名空间并直接调用 start-server 来启动此应用程序。 :gen-class 关键字和 -main 函数意味着应用程序也可以打包为 uberjar,以便从 REPL 外部启动。 (REPL 之外还有一个世界?好吧,无论如何,有些人已经要求了......)

This is quite an old question, and there have been some recent changes that make this much easier.

There are two main things that you want:

  1. Control should return to the REPL so you can keep interacting with your server. This is accomplished by adding {:join? false} to options when starting the Jetty server.
  2. You'd like to automatically pick up changes in certain namespaces when the files change. This can be done with Ring's "wrap-reload" middleware.

A toy application would look like this:

(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))

The wrap-reload function decorates your app routes with a function that detects changes in the listed namespaces. When processing a request, if those namespaces have changed on disk, they are reloaded before further request processing. (My "view" namespace is dynamically created by Fleet, so this auto-reloads my templates whenever they change, too.)

I added a few other pieces of middleware that I've found consistently useful. wrap-file handles static assets. wrap-file-info sets the MIME type on those static assets. wrap-stacktrace helps in debugging.

From the REPL, you could start this app by using the namespace and calling start-server directly. The :gen-class keyword and -main function mean that the app can also be packaged as an uberjar for startup from outside the REPL, too. (There's a world outside the REPL? Well, some people have asked for it anyway...)

月隐月明月朦胧 2024-08-17 06:59:01

这是我从 Compojure Google 群组中的 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

Here's an answer I got from James Reeves in the Compojure Google Group (the answer's here with his permission):

You can reload a namespace in Clojure using the :reload key on the use
or require commands. For example, let's say you have a file "demo.clj" that contains your routes:

(ns demo 
  (:use compojure))

(defroutes demo-routes 
  (GET "/" 
    "Hello World") 
  (ANY "*" 
    [404 "Page not found"])) 

At the REPL, you can use this file and start a server:

user=> (use 'demo) 
nil 
user=> (use 'compojure) 
nil 
user=> (run-server {:port 8080} "/*" (servlet demo-routes)) 
... 

You could also put the run-server command in another clojure file.
However, you don't want to put it in the same file as the stuff you want to reload.

Now make some changes to demo.clj. At the REPL type:

user=> (use 'demo :reload) 
nil 

And your changes should now show up on http://localhost:8080

拧巴小姐 2024-08-17 06:59:01

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

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

  2. 创建项目

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

    :ring {:handler compojure-test.handler/app 
           : 自动重新加载?真的
           : 自动刷新?真的}
    
  4. 在您想要的任何端口上启动服务器

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

I wanted to add an answer, since things have changed a bit since the newest answer and I had spent a bit of time looking for this myself.

  1. Install leiningen (just follow the instructions there)

  2. Create project

    lein new compojure compojure-test 
    
  3. Edit the ring section of project.clj

    :ring {:handler compojure-test.handler/app 
           :auto-reload? true
           :auto-refresh? true}
    
  4. Start the server on whatever port you want

    lein ring server-headless 8080
    
  5. Check that the server is running in your browser, the default base route should just say "Hello world". Next, go modify your handler (it's in src/project_name). Change the hello world text, save the file and reload the page in your browser. It should reflect the new text.

童话里做英雄 2024-08-17 06:59:01

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

Following up on Timothy's link to Jim Downing's setup, I recently posted on a critical addition to that baseline that I found was necessary to enable automatic redeployment of compojure apps during development.

夏九 2024-08-17 06:59:01

我有一个如下所示的 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。

I have a shell script that looks like this:

#!/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 looks like this

(use '[swank.swank])                                                                                                                        
(swank.swank/ignore-protocol-version "2009-03-09")                                                                                          
(start-server ".slime-socket" :port 4005 :encoding "utf-8")

Whenever I want to update the server I create an ssh tunnel from my local machine to the remote machine.

Enclojure and Emacs (running SLIME+swank-clojure) can connect to the remote REPL.

丑丑阿 2024-08-17 06:59:01

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

  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. 重新定义问候语
    (取消路由迎接者
    (得到 ”/”
    (html [:h1 "再见世界"])))

  6. 检查 localhost:8080

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

This is highly configuration dependent but works for me and I think you can adapt it:

  1. Put compojure.jar and the jars under the compojure/deps directory are in your classpath. I use clojure-contrib/launchers/bash/clj-env-dir to do this, all you need to do is set the directory in CLOJURE_EXT and it will find the jars.
    CLOJURE_EXT Colon-delimited list of paths to directories whose top-level
    contents are (either directly or as symbolic links) jar
    files and/or directories whose paths will be in Clojure's
    classpath.

  2. Launch clojure REPL

  3. Paste in hello.clj example from compojure root directory

  4. Check localhost:8080

  5. Re-define the greeter
    (defroutes greeter
    (GET "/"
    (html [:h1 "Goodbye World"])))

  6. Check localhost:8080

There are also methods for attaching a REPL to an existing process, or you could keep a socket REPL embedded in your server or you could even define a POST call that will eval on the fly to allow you to redefine functions from the browser itself! There are lots of ways to approach this.

始终不够爱げ你 2024-08-17 06:59:01

我想跟进 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 服务器正常工作至关重要

I'd like to follow up on mtnygard's answer and post the full project.clj file and core.clj file that got the given functionality working. A few modifications were made, and it's more barebones

pre-setup commands

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))

Pay Attention to the change from (defn app ...) to (def app ...)

This was crucial to getting the jetty server to work correctly

怀里藏娇 2024-08-17 06:59:01

Compojure 在内部使用 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}

Compojure uses ring internally (by the same author), the ring web server options allow automatic realoading. So two alternatives would be :

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

Note that :

You need to have a line in your project.clj file that looks like:
:ring {:handler your.app/handler}

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文