Разработка Compojure без перезагрузки веб-сервера

Ранее я писал небольшое приложение Swing в Clojure, и теперь я хотел бы создать веб-приложение в стиле Ajax. Compojure сейчас выглядит как лучший выбор, так что вот что я собираюсь попробовать.

Мне бы хотелось иметь очень маленькую петлю обратной связи редактирования / попытки, поэтому я бы предпочел не перезапускать веб-сервер после каждого небольшого изменения, которое я делаю.

Какой лучший способ сделать это? По умолчанию мои настройки Compojure (стандартные вещи с ant deps / ant с Jetty), похоже, не перезагружают какие-либо изменения, которые я делаю. Мне придется перезапустить с run-server, чтобы увидеть изменения. Из-за Java-наследия и способа запуска системы и т. Д. Это, вероятно, совершенно нормально, и так должно быть, когда я запускаю систему из командной строки.

Тем не менее, должен существовать способ динамической перезагрузки во время работы сервера. Должен ли я использовать Compojure из REPL для достижения моей цели? Если я должен, как я могу перезагрузить мои вещи там?

8 ответов

Это довольно старый вопрос, и в последнее время произошли некоторые изменения, которые значительно облегчили его.

Есть две основные вещи, которые вы хотите:

  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 украшает маршруты вашего приложения функцией, которая обнаруживает изменения в перечисленных пространствах имен. При обработке запроса, если эти пространства имен изменились на диске, они перезагружаются перед дальнейшей обработкой запроса. (Мое пространство имен "view" динамически создается Fleet, поэтому это автоматически перезагружает мои шаблоны при каждом их изменении.)

Я добавил несколько других промежуточных программ, которые я всегда находил полезными. wrap-файл обрабатывает статические ресурсы. wrap-file-info устанавливает тип MIME для этих статических ресурсов. wrap-stacktrace помогает в отладке.

Из REPL вы можете запустить это приложение, используя пространство имен и напрямую вызывая start-server. Ключевое слово: gen-class и функция -main означают, что приложение также может быть упаковано как uberjar для запуска из-за пределов REPL. (Есть мир за пределами REPL? Ну, некоторые люди все равно просили об этом...)

Вот ответ, который я получил от Джеймса Ривза из Compojure Google Group (ответ здесь с его разрешения):

Вы можете перезагрузить пространство имен в Clojure, используя ключ: reload в команде use или require. Например, допустим, у вас есть файл "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/

Я хотел добавить ответ, поскольку с момента появления нового ответа все немного изменилось, и я потратил немного времени на его поиск.

  1. Установите leiningen (просто следуйте инструкциям там)

  2. Создать проект

    lein new compojure compojure-test 
    
  3. Отредактируйте кольцевой раздел project.clj

    :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, сохраните файл и перезагрузите страницу в браузере. Он должен отражать новый текст.

Следуя ссылке Тимоти на настройки Джима Даунинга, я недавно написал о критическом дополнении к этому базовому уровню, которое, по моему мнению, было необходимо для автоматического повторного развертывания приложений compojure во время разработки.

У меня есть сценарий оболочки, который выглядит так:

#!/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.

Это сильно зависит от конфигурации, но работает для меня, и я думаю, что вы можете адаптировать его:

  1. Поместите compojure.jar, и файлы jar в каталоге compojure/deps находятся в вашем classpath. Для этого я использую clojure-contrib/launchers/bash/clj-env-dir, все, что вам нужно сделать, это установить каталог в CLOJURE_EXT, и он найдет фляги. CLOJURE_EXT Разделенный двоеточиями список путей к каталогам, содержимое верхнего уровня которых (непосредственно или в виде символических ссылок) содержит файлы jar и / или каталоги, пути которых будут в пути к классам Clojure.

  2. Запустить clojure REPL

  3. Вставьте пример hello.clj из корневого каталога compojure.

  4. Проверьте localhost:8080

  5. Переопределить приветствующего (удаляет приветствующего (GET "/" (html [:h1 "Goodbye World"])))

  6. Проверьте localhost:8080

Есть также методы для присоединения REPL к существующему процессу, или вы можете оставить REPL сокета встроенным в ваш сервер, или вы даже можете определить вызов POST, который будет выполняться на лету, чтобы позволить вам переопределять функции из самого браузера! Есть много способов приблизиться к этому.

Я хотел бы проследить за ответом 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))

Обратите внимание на изменение с (def app ...) на (def app ...)

Это было важно для правильной работы сервера Jetty

Compojure использует кольцо внутри (того же автора), опции кольцевого веб-сервера допускают автоматическое перенаправление. Таким образом, две альтернативы будут:

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

Обратите внимание, что:

В вашем файле project.clj должна быть строка, которая выглядит следующим образом:
: ring {: handler your.app/handler}

Другие вопросы по тегам