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

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

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

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

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

Ответ 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? Ну, некоторые люди просили об этом в любом случае...)

Ответ 2

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

Вы можете перезагрузить пространство имен в 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 {:handler compojure-test.handler/app 
           :auto-reload? true
           :auto-refresh? true}
    
  • Запустите сервер на любом порту, который вы хотите

    lein ring server-headless 8080
    
  • Убедитесь, что сервер работает в вашем браузере, базовый маршрут по умолчанию должен просто сказать "Hello world". Затем измените обработчик (он в src/project_name). Измените текст приветствия, сохраните файл и перезагрузите страницу в своем браузере. Он должен отражать новый текст.

Ответ 5

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

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

Ответ 6

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

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

  • Запуск clojure REPL

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

  • Проверить localhost: 8080

  • Переопределите greeter (разворачивает приветствие        (ПОЛУЧИТЬ "/"                 (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}