Контекст
Производственный REPL не для слабонервных. Я бы не стал добавлять REPL в производство в 95% проектов, частью которых я был; однако в настоящее время я работаю с двумя другими разработчиками, которым полностью доверяю возможности производственного REPL. Как простое правило, я бы не дал доступ к производственному REPL любому, у кого нет корневого доступа к серверу — если вам нельзя доверять с одним, скорее всего, вам нельзя доверять с помощью разное. Однако, если вам можно доверять, я бы предпочел, чтобы у вас были все доступные инструменты.
Боковая панель : самая интересная история, которую я слышал о REPL в prod, была следующей: отладка программы, работающей на оборудовании стоимостью 100 миллионов долларов, которое находится на расстоянии 100 миллионов миль, является интересным опытом. Наличие цикла чтения-проверки-печати на космическом корабле оказалось неоценимым в поиске и устранении проблемы. —
Лиспинг в JPL
Не поймите меня неправильно, я не регулярно пересматриваю функции в производстве. Я редко использую продукт REPL, но когда я делаю, я почти всегда использую его для чтения справочных данных. В очень редком случае я оберну существующую функцию новой версией, которая регистрирует значение входящих аргументов. Короче говоря, я использую продукт REPL (опять же, экономно) для сбора дополнительных данных, а не для исправления ошибок. Я не говорю, что никогда не использовал бы это, чтобы исправить ошибку, но это был бы чрезвычайно исключительный случай.
Код
На самом деле очень легко подключить веб-REPL к приложению Clojure, которое уже имеет веб-интерфейс. Приложения Clojure, над которыми я работаю, обычно имеют веб-сокеты, а в следующем решении используются веб-сокеты; однако нет никаких причин, по которым вы не могли бы использовать простые веб-запросы и ответы. Наконец, я на самом деле не писал этот код. Кто-то из
DRW написал это (я понятия не имею, кто), и с тех пор его копировали несколько раз. Без лишних слов, код:
<!DOCTYPE HTML>
<html>
<head>
<link href="css/web-repl.css" rel="stylesheet" type="text/css">
<script src="js/lib/jquery-1.5.min.js" type="text/javascript"></script>
<script src="js/lib/jquery.websocket-0.0.1.js" type="text/javascript"></script>
<script src="js/lib/jquery.console.js" type="text/javascript"></script>
<script src="js/web-repl.js" type="text/javascript"></script>
<title>Web REPL</title>
</head>
<body>
<div id="console"/>
</body>
</html>
var ws = null;
$(document).ready(function () {
ws = $.websocket("ws://" + window.location.host + "/websocket", {
events: {
'web-repl-response': function(info) {
currentCallback([
{msg: info.response,
className:"jquery-console-message-value"}
]);
}
}
});
$("#console").console({
promptLabel: 'Clojure> ',
commandValidate:function(line) {
if (line == "") {
return false;
}
else {
return true;
}
},
commandHandle:function(line, callback) {
currentCallback = callback;
ws.send('selfish', {type: "web-repl", command: line});
},
welcomeMessage:'Enter some Clojure code, and it will be evaluated ON THE SERVER -- CAREFUL!!!.',
autofocus:true,
animateScroll:true,
promptHistory:true
})
});
(ns web-repl
(:require clojure.main)
(:use [clojure.stacktrace :only [root-cause]]))
(defonce repl-sessions (ref {}))
(defn current-bindings []
(binding [*ns* *ns*
*warn-on-reflection* *warn-on-reflection*
*math-context* *math-context*
*print-meta* *print-meta*
*print-length* *print-length*
*print-level* *print-level*
*compile-path* (System/getProperty "clojure.compile.path" "classes")
*command-line-args* *command-line-args*
*assert* *assert*
*1 nil
*2 nil
*3 nil
*e nil]
(get-thread-bindings)))
(defn bindings-for [session-key]
(when-not (@repl-sessions session-key)
(dosync
(commute repl-sessions assoc session-key (current-bindings))))
(@repl-sessions session-key))
(defn store-bindings-for [session-key]
(dosync
(commute repl-sessions assoc session-key (current-bindings))))
(defmacro with-session [session-key & body]
`(with-bindings (bindings-for ~session-key)
(let [r# ~@body]
(store-bindings-for ~session-key)
r#)))
(defn do-eval [txt session-key]
(with-session session-key
(let [form (binding [*read-eval* false] (read-string txt))]
(with-open [writer (java.io.StringWriter.)]
(binding [*out* writer]
(try
(let [r (pr-str (eval form))]
(str (.toString writer) (str r)))
(catch Exception e (str (root-cause e)))))))))
#console {
height: 820px;
background: #eee;
margin: 10px;
border-radius: 5px;
-moz-border-radius: 5px;
border: 1px solid #aaa;
}
#console div.jquery-console-inner {
height: 800px;
margin: 10px 10px;
overflow: auto;
text-align: left;
}
#console div.jquery-console-welcome {
color: #ef0505;
font-family: sans-serif;
font-weight: bold;
padding: 0.1em;
}
#console div.jquery-console-message-value {
color: #0066FF;
font-family: monospace;
padding: 0.1em;
}
#console div.jquery-console-prompt-box {
color: #444;
font-family: monospace;
}
#console div.jquery-console-focus span.jquery-console-cursor {
background: #333;
color: #eee;
font-weight: bold;
}
#console div.jquery-console-message-error {
color: #ef0505;
font-family: sans-serif;
font-weight: bold;
padding: 0.1em;
}
#console div.jquery-console-message-success {
color: #187718;
font-family: monospace;
padding: 0.1em;
}
#console span.jquery-console-prompt-label {
font-weight: bold;
}
; the code I work with has a publish-fn that we use to publish json back to a web-socket. ; therefore, the following line is all we need to add to our application to get the web REPL working (publish-fn (hash-map :type :web-repl-response :response (web-repl/do-eval command publish-fn))))