Этот раздел начинает тему методологии работы с сервером контроля и управления jCjS.

Некоторая историческая справка

На заре развития интернет задача HTTP серверов ограничивалась выдачей клиентам статических файлов с HTML разметкой, а запрос URL, соответственно, указывал на некоторую ветвь локальной файловой системы сервера, доступной для всеобщего обозрения. Такой функционал был достаточен для обмена информацией в научном сообществе, но как говорится, аппетит приходит во время еды, и очень скоро стало понятно, что на один и тот же запрос URL сервер должен иметь возможность выдавать различную информацию. Ттипичный пример: подсчет количества посещений некоторой страницы, каждый новый запрос к странице будет содержать новое значение счетчика. С точки зрения компьютерной науки, можно утверждать, что сервер в данном случае обладает неким внутренним состоянием, определяемым переменной счетчика. Возникают вопросы: где хранится и кто выполняет программу содержащую: counter = counter + 1; ? Изначально, и до сих пор, в задачу классических веб-серверов не входит выполнение каких-либо программ, как и декларативный формат HTML не предусматривал содержание в себе программного кода.

Cейчас существуют два основный подхода к решению обозначенной проблемы: во первых классическим веб-серверам было дозволено призывать к исполнению некие программы, написанные на любом существующем в природе программном языке, интерпретируемом или компилируемом, или даже скрипты командной оболочки shell. Универсальность такого подхода состоит в том, что поток стандартного вывода (элементарная оператор print) оказывается подключенной не к экрану, а к сокету сетевого соединения с клиентом. Так работает технология CGI и fastCGI. Во-вторых, появился язык PHP, который реализует обработку программных вставок находящимися в специальных тегах HTML. Такие файлы имеют суффикс *.php и веб-сервер прежде чем их отдать клиенту запускает над ними обработку PHP интерпретатора. В любом случае, веб-сервер исторически стараются избавить от выполнения каких-либо программ, следуя принципам надежности и производительности.

Далее, по мере дальнейшего роста аппетита в плане возможностей интернет технологий, оказалось, что не плохо бы, чтобы загруженная интернет страница вела себя как программа, а не как просмотрщик файла: откликалась на действия пользователя, что-то менялось в отображении с течением времени, и самостоятельно производила запросы серверу. Возвращаясь к счетчику посещений, это означает, что рост счетчика посещения страниц можно будет наблюдать в реальном времени, без перезагрузки страницы.

Дальнейшей ступенью описанной выше 20 летней эволюции интернета в сторону программного подхода к формированию содержимого страниц, можно считать применение интерпретатора JavaScript на стороне сервера. Рассматриваемый здесь jCjS является таким примером, более того он методологически "заточен" для применения интернет стандартов для контроля и управления различным оборудованием. Это значит, что при формировании интернет страниц задействуется оперативная информация о состоянии оборудования, полученная низкоуровневым бинарным обменом, а HTTP запросы серверу являются по-сути "удаленным нажатием кнопок".

Уникальность JavaScript

На самом деле JavaScript при всей синтаксической схожести с С/С++, С# и Java концептуально не похож на них. Программисты языков упомянутого списка на начальном этапе использования JavaScript пытаются его использовать в привычном для них стиле, тем более синтаксис JavaScript к этому располагает. Но, на самом деле, JavaScript является носителем совершенно другого подхода к разработке программ и, можно сказать, требует совершенно другого образа мысли. Не во всех пособиях по JavaScript это явно отмечено, - обычно рассматривается только синтаксис JavaScript и его применение на стороне браузера. Сейчас мы попробуем исправить этот пробел.

Первое, что нужно отметить, это то, что JavaScript трактует функции как объекты первого класса. Если Вы впервые встречаетесь с этим понятием, то даже, если проследуете по ссылке: объект первого класса и внимательно ее прочитаете, Вы можете не ощутить колоссальную мощь JavaScript, заключенную в термине "объект первого класса". Смотрите: функции, которые в других языках должны быть определены до начала выполнения программы, в JavaScript могут быть внедрены в момент выполнения, более того они могут быть сгенерированы самой программой, и этой же программой выполнены. Если Вам в голову пришла мысль о реализации самообучаемой программы, - Вы на правильном пути, но дело не только в этом. Следует еще отметить, что JavaScript интерпретатору могут передаваться порции кода в любое время и в любой последовательности. При этом результат выполнения очередной порции кода, выраженный в наборе переменных, объектов и функций не исчезает бесследно, а остается в контексте интерпретатора и может быть использован следующей отдельной порцией кода. Это подход сильно отличается от представления о программе как исполняемой сущности с входными и выходными данными. JavaScript интерпретатор, в этом случае, более похож на некоторый автомат, состояние которого меняется с каждой порцией переданной ему на исполнение программного кода. В промежутках между подачами кода состояние интерпретатора неизменно. Благодаря таким свойствам JavaScript, он является идеальным средством для построения программной модели различных автоматизированных установок, требующих контроля и управления (с jCjS - удаленного). При этом появляется возможность внесения изменений в программый код без остановки как самих установок, так и компьютера под управлением которых они каходятся. Встроенный в jCjS HTTP сервер здесь может служить средством доставки порций кода до интерпретатора JavaScript.

Облачный пост

Пусть имеется некоторый пост с командным обработчиком следующего содержимого:

(function() {
   if(http.method == 'POST') {
      if(http.postType == 'multipart/form-data') {
         for(var i = 0; i < http.postFragsCnt; i++) {
            if(http.postFragName(i) == 'script') {
               var res = eval(http.postFragBodyStr(i));
               http.respPlainText(res);
               return;
            }
         }
      } else {
         var res = eval(http.postBodyStr);
         http.respPlainText(res);
         return;
      }
   }
   if(http.method == 'GET') {
      if(http.pathCnt == 0) {
         if(http.argCnt == 0) {
            http.respFile('stuff@examples/methodics/cloud/index.html');
            return;
         } else if(http.argCnt == 1) {
            if(http.argKeyByNum(0) == 'script') {
               var res = eval(jsext.fromBase64Str(http.argByNum(0)));
               http.respPlainText(res);
               return;
            }
         }
      }
   }
   http.respError(404);
})();

Инициализирующий скрипт - отсутствует, имеется только командный. Обратите внимание, что здесь представлено два варианта засылки скрипта на сервер: методом GET и методом POST. В принципе не важно какой из способов Вы выберете для своих постов в дальнейшем, необходимо только понимать, что отправка текста скрипта методом GET в чистом виде не получится, его необходимо перекодироваь в строку не содержащую пробелов, специальных символов, переводов строк, поэтому используется base64 кодирование на стороне браузера, и base64 раскодирование на стороне сервера. Если использовать отправку методом POST, то необходимо отметить, что есть множество форматов метода POST и только один из них multipart/form-data объект соединения http автоматически разбирает на фрагменты. В дальнейшем мы здесь мы будем использовать метод GET с base64 кодированием.

Соответствующая HTML страница stuff@examples/methodics/cloud/index.html:

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Облачный пост</title>
<script language="javascript" type="text/javascript" src="/jsl/jquery.min.js"></script>
<script language="javascript" type="text/javascript" src="/js/base64.js"></script>
<script language="javascript" type="text/javascript">

function getEvalJs(codeJs) {
   $.get('?script=' + Base64.encode(codeJs))
   .done(doneEvalJs)
   .error(errorEvalJs);
}
function postEvalJs(codeJs) {
   $.post('', codeJs)
   .done(doneEvalJs)
   .error(errorEvalJs);
}
function doneEvalJs(ans) {
   alert(ans);
}
function errorEvalJs() {
   alert('error');
}

</script>
</head>

<body> <!-- onload="startPage()"> -->

<form enctype="multipart/form-data" method="post">
Введите текст скрипта для выполнения на сервере:<br>
<textarea id=codejS name=script rows=4 cols=80></textarea><br>
<input type="submit" value="Отправить на выполнение методом POST multipart/form-data"></p>
</form>

<input id=evalGet type="button" name="evalPost" value="Отправить на выполнение методом GET" onclick="getEvalJs($('#codejS').val())"><br>
<input id=evalPost type="button" name="evalPost" value="Отправить на выполнение методом POST" onclick="postEvalJs($('#codejS').val())">

</body>
<html>

Испытываем, в открывшейся странице поста появится поле для ввода программного кода скрипта, который будет выполнен интерпретатором поста jCjS. Наберите в нем, например, 2 и любую из кнопок "GET for eval" или POST for eval. Возникнет всплывающее окно с ответом 2. Можно экспериментировать далее: 2 + 2, 2 * 2, Math.PI; a = 2; b = a; и так далее... С помощью jCjS мы получили некоторый облачный калькулятор, интересно то, что мы получили не простой калькулятор, а программируемый. Действительно, выполним в поле кода:

   add = function(a, b) { return a + b; }

(Замечание: сигнатура определения функции function add(a, b) { return a + b; } - не подходит, функция должна быть определена глобально).

Далее набираем add(1, 2), жмем любую кнопку, получаем ответ. Можно очень просто заменить поведение функции add, просто набрав:

   add = function(a, b) { return a * b; }

Если бы мы использовали на стороне сервера другой язык программирования, который не рассматривает функции как объекты первого уровня, то для замены функционала сервера нам бы пришлось действовать примерно так: мы бы использовали оператор switch, в котором выбирали функцию в зависисмости от значения некоторой переменной. Но как бы не был велик набор запрограммированных функций, он все равно никогда не может охватить бесконечного разнообразия в назначении поведения функции который нам предоставляет JavaScript.

Таймер - инициатор изменения внутреннего состояния

Ранее мы говорили о том, что между подачами кода состояние интерпретатора JavaScript неизменно, однако вызов на исполнение скрипта или некоторой функции может произойти не только в результате подачи команды со стороны сервера, но и в результате внутренних событий сервера, например, срабатывания события timeout объекта таймера. Мы неоднократно ранее использовали его, поэтому для повторения, наберем следующий код в нашем облачном калькуляторе:

cnt = 0;
post.timer = new Timer();
post.timer.timeout.connect(func);
function func() {
  cnt++;
}
post.timer.start(1000);

Убедимся что все работает, набрав в поле кода cnt и вызывав его к исполнению.

Интересный факт: ни один из встроенных в JavaScript (нативных) объектов не может инициировать вызов скрипта или функции так, как это делает таймер или другой объект jCjS (SerialPort, SerialTask). Разработчики JavaScript оставили такую возможность целиком и полностью за средой в которую интерпретатор JavaScript будет встроен. Кстати, синтаксис таймерных функций в браузерах совершенно другой: setTimeout(func/code, delay[, arg1, arg2...]), setInterval(func/code, delay[, arg1, arg2...]), да и поведение тоже немного другое.