Справка по демонстрационной системе Web4Daq.


Сначала рекомендуется почитать про HTTP и про CGIтут). Без этого понять что-либо будет сложно.

Демонстрационная система Web4Daq служит для иллюстрации того, как в системе CRW-DAQ можно организовывать распределенные системы управления, построенные по принципу клиент-сервер, с использованием Web-технологий.

Допустим, имеется сервер, то есть программа, которая умеет делать что-то содержательное, скажем, измерять и управлять физ. установкой. Допустим, также, что имеется несколько удаленных клиентов, то есть пользователей, которые могут располагаться как рядом с установкой и работать на локальной машине, так и быть удаленными пользователями в сети. В ряде случаев по условиям эксплуатации пользователи находиться рядом с компьютером просто не могут (радиация, химия, шум и т.д.). Возможно также, что несколько операторов в разных местах хотят наблюдать результаты измерений или управлять установкой. Таким образом, возникает СЕРВЕР - процесс, в котором работает программа измерений и произвольное число КЛИЕНТОВ - процессов, в которых запущен интерфейс пользователя. Клиент может быть локальным (на той же машине) или удаленным (на другой машине в сети). Задача состоит в том, чтобы клиенты могли получать от сервера измеряемые данные, а также посылать серверу команды для управления установкой. Такова, вкратце, формулировка задачи.

Клиентская часть - интерфейс пользователя - может быть построена на основе HTML - агента, например, Internet Explorer. Главным достоинством такого подхода является то, что клиентское программное обеспечение писать не надо, оно уже готово, причем практически все системы гарантированно поддерживают просмотр HTML. Всю содержательную работу при этом выполняет сервер. Хотя написание серверной части существенно усложняется, в целом задача становится проще, потому что на один сервер обычно приходится много клиентов. С каждым клиентом связана необходимость установки и настройки клиентского программного обеспечения, все это время.

Правда, надо сказать, что HTTP - довольно медленный протокол, он не пригоден для систем реального времени. Его можно применять, когда речь идет о длительных процессах, с временем реакции в несколько секунд. В этом смысле Web - технологии не конкурируют, а дополняют технологии DIM и OPC, которые присутствуют в CRW-DAQ. Кроме того, протокол HTTP предполагает, что активной стороной при обмене данными всегда выступает клиент. Клиент посылает запрос, а сервер только отвечает на клиентские запросы. Для систем реального времени характерна обратная ситуация - сервер, по мере получения результатов измерений и наступления разных событий рассылает клиентам сообщения и обновляет данные. Именно так работают, например, DIM и OPC. Единственной возможностью автоматического обновления клиентских данных при использовании Web является периодическая посылка серверу HTTP запросов на обновление HTML страницы. При этом реакция составляет несколько секунд. Это накладывает ограничение на задачи, в которых Web технологии вообще применимы.

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

В данном демонстрационном примере СЕРВЕР умышленно примитивен в содержательной части: это просто генератор двух якобы "измеряемых сигналов" - синусоиды sin_wave1 и косинусоиды sin_wave2, с частотой sin_freq, амплитудой sin_ampl и искуственным шумом sin_noise, которые моделируют "управляющие параметры" воздействия оператора. При нажатии кнопки sin_start программа начинает генерировать "измеренные" сигналы, при выключении кнопки "измерения" останавливаются. При нажатии кнопки sin_browse программа вызывает Internet Explorer. Клиенты на локальной или удаленной машине должны видеть кривые, значения управляющих параметров, а также иметь возможность их менять (с системой прав доступа).

Если бы речь шла только о локальном клиенте, все было бы просто, события идентифицировались бы просто кнопкой и именем нажатого сенсора (clickbutton, clicksensor). Вся конфигурация и программа заняла бы пару десятков строк. На эту тему есть много примеров. С распределенной системой все сильно усложняется. Клиентов много, они работают одновременно на разных машинах, под разными пользователями, которые имеют разные права и жмут на все кнопки подряд во всех доступных окнах. Понятие события и его идентификация существенно усложняется. Этот факт не зависит от применяемой системы программирования, просто распределенные многопользовательские системы вообще по сути своей всегда сложнее локальных.

Когда пользователь вызывает HTML страницу в Internet Explorer, Web-сервер получает HTTP запрос. Обычно Web-сервер не обрабатывает запрос сам (кроме самых тривиальных), а передает его CGI - скрипту (от Common Gateway Interface). CGI-скрипт обрабатывает запрос и формирует HTML - текст страницы, которую Web-сервер возвращает клиенту. Так что клиенту увидит то, что ему предоставил CGI - скрипт.

Такая технология принята потому, что написание Web-сервера намного сложнее, чем написание CGI-скрипта. Обычно используется готовый Web-сервер. В CRW-DAQ используется TinyWeb - сервер:

  • TinyWeb Copyright (C) 2000-2004 RITLABS S.R.L. http://www.ritlabs.com/tinyweb/

  • который (цитата) "is free for commercial and non-commercial use". Условием использования является сохранение авторской ссылки. Сервер доступен в исходных кодах (на Delphi), компилируется под CRW-DAQ.

    С CGI - скриптом дело сложнее. Собственно CGI - скрипт написан раз и навсегда (он находится в дистрибутиве CRW-DAQ). Он тоже сам по себе делает довольно мало, подключается по именованному каналу к CRW-DAQ, передает ему HTTP запрос и ждет ответа. Запрос принимает программа WebSrv.pas, которая принадлежит устройству &WebSrv. Имя устройства предлагается считать фиксированным. Сама программа WebSrv.pas тоже считается стандартной и не меняется. Далее, устройство анализирует и расшифровывает запрос и помещает его в текст. Затем формируется сообщение

      @HTTP_REQUEST_ACCEPTED=sender,reqtime,request,reply
       sender  - имя устройства, то есть &WebSrv
       reqtime - время запроса по часам msecnow
       request - ссылка на текст запроса, которую нужно использовать в вызовах
                 функций типа text_getln(...) для извлечения информации о HTTP запросе.
       reply   - ссылка на текст ответа, которую нужно использовать в вызовах
                 функций типа text_getln(...) для формирования HTML ответа. 
      Example:
       @HTTP_REQUEST_ACCEPTED=&WEBSRV,63295844422438,1049397,1049374			 
      
    Это сообщение рассылается клиентам (см. далее). Клиент должен сформировать в тексте, ссылку на которую разослал WebSrv, текст HTML страницы. Последней строчкой страницы должна быть строка dump(0). Эта строчка служит для WebSrv признаком того, что страница сформирована и ее можно отсылать. То есть код обработки будет примерно такой:
      readln(Line);
      if ExtractWord(1,Line)='@HTTP_REQUEST_ACCEPTED' then begin
       Reply:=Val(ExtractWord(5,Line));
       b:=text_addln(Reply,'<html>');
       b:=text_addln(Reply,'<head><title>Example: hello.</title></head>');
       b:=text_addln(Reply,'<body> HELLO, WORLD! </body>');
       b:=text_addln(Reply,'</html>');
       b:=text_addln(Reply,dump(0));   {end of text marker}
      end;
      
    При этом элемент <html> ... </html> строго обязателен.

    Со стороны клиента это все. Дальше программа WebSrv увидит маркер конца текста и передаст страницу CGI-скрипту, тот передаст его Web-серверу, который передаст его в Internet Explorer. То есть цепочка такая:

       Client(IE) ---> TinyWeb ---> WebSrv.cgi ---> WebSrv.Pas(CRW-DAQ)
       Click URL  HTTP         CGI             Pipe  `->send the message to clients
                                                    @HTTP_REQUEST_ACCEPTED=...
                                                    Wait until Reply ready ...
                                                    Somebody put HTML text to Reply
                                                    and Chr(0) as EndOfText marker.
       Client(IE) <--- TinyWeb <--- WebSrv.cgi <--- WebSrv.Pas(CRW-DAQ)
       See HTML   HTTP         CGI             Pipe
      
    Все это выглядит замысловато, но ведь примерно так работает большинство Web-серверов в мире, с той разницей, что вместо DaqPascal скрипт пишется на PHP или Perl. Важно, что Web-сервер писать не надо, это сильно облегчает работу.

    Вот, собственно, и все. Дальше идут детали и нюансы, и их очень много.

    Конфигурирование WebSrv

    Сначала надо сконфигурировать сервер. Это не так сложно сделать.

    Конфигурирование клиента (скрипта)

    Клиент, который будет обрабатывать HTTP - запросы, обязан себя зарегистрировать в [&WebSrv] ClientList. Это значит, что WebSrv будет посылать ему сообщения @HTTP_REQUEST_ACCEPTED для обработки. Другой обязательный параметр - TrustedUsers, имя секции с описанием прав доступа. Пример:
      ;**********************************
      ;*** WEB CGI SCRIPT DEMO CONFIG ***
      ;**********************************
      [DeviceList]                           ; Fix=Fixed, Var=Variable params
      &WebCgi = device software program      ; Var &WebCgi device declaration
      [&WebCgi]                              ; Var &WebCgi device section
      Comment       = WEB CGI script DEMO    ; Var &WebCgi device description
      InquiryPeriod = 1                      ; Var &WebCgi device poll period
      DevicePolling = 10, tpNormal           ; Var &WebCgi thread poll period
      ProgramSource = ..\DaqPas\webcgi.pas
      TrustedUsers  = [TrustedUsers]         ; Fix Section where access table
      [&WebSrv]                              ; Fix Section of Web ClientList.
      ClientList    = &WebCgi                ; Var Add himself to ClientList.
      []
      

    Самой сложной частью конфигурации будет таблица прав доступа. Она сделана общей для DIM и WEB, только вместо поля MAC используется Password. Права доступа задаются в таблице примерно такого вида:

      [TrustedUsers]
      ;level     user    host       IP           password
      ;---------------------------------------------------
      web-user   sergey  localhost  127.0.0.1    123      ; локальный user sergey на localhost, пароль 123
      web-root   alex    crwbox     *            321      ; удаленный root alex   на crwbox без проверки IP, пароль 321
      
    Каждая строка таблицы содержит набор (Level, User, Host, IP, Password). Level может быть web-guest, web-user, web-root по возрастанию уровня прав. Это поле строго обязательно. Далее указывается имя пользователя, имя компьютера, его IP-адрес, а также пароль пользователя. Пароль хранится открыто; предполагается, что на серверную машину доступа нет (она ведь удаленная). Считается, что условие доступа выполнено, если все параметры в какой-либо строке удовлетворяют заданному шаблону. При этом в шаблоне символ (*) звездочки означает, что параметр может иметь любое значение, а символ (.) точки - значение параметра на машине сервера. Если пользователь удовлетворяет хотя бы одной строке таблицы, он получает права доступа этого уровня. Если прав доступа не получено, сервер посылает уведомление об отказе в обслуживании запроса.

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

    Содержание HTTP-запроса

    Вот пример запроса:
       QUERY.COUNT=1
       QUERY.ITEM0=action=echo
       COOKIE.COUNT=2
       COOKIE.ITEM0=RemoteUserName=fhJW7Y1eSfmCNvuGEe2w9eiQC0C/85woHa+uirM8qlM
       COOKIE.ITEM1=RemotePassword=GXY0lxYhBnDAYD5w77TaASAf6F+//gI50OXam/QBVoM
       CONTENT.COUNT=2
       CONTENT.ITEM0=RemoteUserName=root
       CONTENT.ITEM1=RemotePassword=123
       GATEWAY_INTERFACE=CGI/1.1
       REQUEST_METHOD=GET
       QUERY_STRING=action=echo
       CONTENT=RemoteUserName=root&RemotePassword=123
       CONTENT_LENGTH=26
       SERVER_NAME=RITLABS S.R.L.
       SERVER_PORT=23816
       SERVER_PROTOCOL=HTTP/1.1
       SERVER_SOFTWARE=TinyWeb/1.93
       REMOTE_HOST=localhost
       REMOTE_ADDR=127.0.0.1
       SCRIPT_NAME=/cgi-bin/websrv.cgi
       PATH_INFO=/websrv.cgi
       PATH_TRANSLATED=D:\DAQ32\DEMO_WEB4DAQ\websrv.cgi
       WEBSRV.NAME=&WEBSRV
       WEBSRV.SITE=http://main/
       WEBSRV.ROOT=D:\DAQ32\DEMO_WEB4DAQ
       WEBSRV.PORT=80
       WEBSRV.INDEX=D:\DAQ32\DEMO_WEB4DAQ\INDEX.HTM
      
    Наиболее важные поля здесь:

    Процедура авторизации

    Когда удаленный клиент открыл Internet Explorer, его список Cookie пуст. При посылке запроса
    http://host/cgi-bin/websrv.cgi?Action=Login
    сервер получает QUERY_STRING = Action=Login и переходит к авторизации. Если в Cookie нет полей RemoteUserName, RemotePassword, то пользователю выдается страница для заполнения формы с именем и паролем. После посылки формы, когда сервер увидит в полях CONTENT параметры UserName, Password, он формирует страницу типа "Access granted!", в которую вставляются Cookie через meta-тег, типа такого
      <meta http-equiv="Set-Cookie" content="RemoteUserName=Alex">
      <meta http-equiv="Set-Cookie" content="RemotePassword=123">
      
    Пароль, конечно, кодируется (тут он показан для наглядности). После этого клиент (пока не закроет окно Internet Explorer) будет включать в HTTP запрос Cookie, содержащие посланные ранее поля RemoteUserName, RemotePassword. Сервер использует эти поля для ответа на вопрос "ты хто такой" и предоставления прав доступа. Каждый запрос, таким образом, анализирует Cookie. Если предоставленная там информация неверна, сервер инициирует сброс Cookie и возврат к авторизации. Для обеспечения безопасности имя пользователя и пароль шифруются. Ключ шифрования знает только сервер, он имеет ограниченный срок действия и периодически меняется.

    Написание кода CGI-скрипта

    Самое простое - воспользоваться примером Web4Daq, это готовый шаблон для разработки Web скриптов. Специфический для данного примера код там небольшой (его придется заменить на свой), а все существенные моменты (авторизация, обработка запроса, формирование ответа и т.д.) там уже сделаны.

    Следует помнить, что в распределенной системе приходится работать с разными копиями одних и тех же данных. Клиентские копии данных могут устаревать. Удаленный клиент, вводящий данные в форму, видит состояние переменных сервера на момент предыдущего запроса. Поэтому код CGI скрипта должен внимательно анализировать данные, присланные клиентом, так как они могут уже не соответствовать реальной ситуации.

    Разные тонкости

    Оценивая возможности и уровень сложности предложенной технологии, следует учитывать, что основная часть кода как клиента, так и сервера уже написана, в "порожденных" системах основная часть кода будет наследоваться с небольшими изменениями. Пример может быть достаточно легко расширен, при увеличении сложности клиента\сервера накладные расходы (код, обслуживающий обмен данными) расти сильно не будут. Поэтому, если один раз "продраться" через довольно непростой код примера, дальше все будет быстро.

    Успехов!