WEB-сервер на Jscript, своими руками/ WSH: работа с сокетами.

В Интернете очень мало документации для работы с сокетами из WSH, а ведь это, под час очень удобное средство для обмена данными между скриптами, запущенными на разных компьютерах сети (либо через интернет), а так же через сокеты можно организовать многопоточность VBS/JS скриптов.

Работа с сокетами будет показана на создании своего собственного нативного HTTP сервера, который будет читать запрос от клиента (в данном случае обычного браузера) обрабатывать его, и на основе анализа выдавать клиенту (браузеру) HTML содержимое запрашиваемой страницы.

Что такое сокет? Сокет это прикладной интерфейс Windows разработанный для обмена данными между хостами по технологии клиент-сервер. Сокет - виртуальная коммуникационная точка которая единовременно, может работать либо на прием либо на передачу данных (иногда, ошибочно, сокетами называют айпи_узла:номер_порта, например 127.0.0.1:80, но это может быть применимо только для протокола IP). В процессе обмена как правило используются два сокета - сокет отправителя и сокет получателя. Хосты могут находиться либо в разных участках сети либо на одном компьютере.

Сразу оговорюсь, из на прямую из Jsript работать с сокетами нельзя, поэтому тут возможны 3 пути:

1.Регестрировать сторонний ActiveX контрол(ActiveX компонент) для работы с API Windows и работать с сокетами через API, что приемлемо но не очень просто и чисто.

2. Использовать MSWINSCK.OCX ActiveX контрол от Майкрософт (поставляется с VB и MS Office). Здесь вроде все нормально, но с одной оговоркой- Js, в силу своей ограниченности, не умеет работать с функцией GetData(), хотя если вы пишете сервер на VBS то все нормально, да и сам контрол требует лицензии.

3.Использовать сторонний контрол, методы которого нормально возвращают параметры в Jscript. Я для этой цели использовал OstroSoft Winsock component (oswinsck.dll) http://www.ostrosoft.com/oswinsck/oswinsck_javascript.asp, по ссылке описание компонента и инструкции по установке.

После установки компонента приступаем к написанию кода.

var bClose=true; 

Переменная-флаг, с помощью которой будем следить за состоянием соединения.

function obInit()
{
oWinsock = new ActiveXObject("OSWINSCK.Winsock");
WScript.ConnectObject(oWinsock, "oWinsock_");
oWinsock.LocalPort=8080;//порт
oWinsock.Protocol=0;// протокол
oWinsock.Listen();// начинаем слушать порт
}

Создаем экземпляр объекта OSWINSCK.Winsock, присоединяемся к нему с целью предоставления доступа к своим событиям - _OnConnectionRequest, _OnDataArrival _Error, Winsock_Close. Далее устанавливаем протокол, порт и начинаем слушать порт (oWinsock.Listen) .

Далее- пишем функции реагирующие на события.

Функция создания соединения:

function oWinsock_OnConnectionRequest(reqId)

{
WScript.Echo("accepting request");
oWinsock.CloseWinsock();

// на всякий случай закрываем все подлючения

WScript.Echo(reqId);
oWinsock.Accept(reqId);

// начинаем соединение

}

После создания соединения управление передается функции для обмена данными.

function oWinsock_OnDataArrival(bytTotal)

{
WScript.Echo("Начинаем прием от клиента...");


var size=0, match1 ;

data= new String("");// буфер запроса получаемого от клиента

fcontent= new String("");// строки считанные с запрашиваемого файла

result= new String(""); // то что будем в результате отдавать клиенту (браузеру)

Объявляем переменные, создаем строковые объекты, давая интерпретатору понять что это именно строки, а не Variant как по умолчанию.

WScript.Echo(bytTotal);

data=oWinsock.GetDataBuffer();// получаем запрос клиента

Обращаю внимание на то, что переменная дата !обязательно! должна быть строкой, иначе могут возникнуть проблемы.

WScript.Echo(data);//выводим запрос клиента в консоль, для наглядности.

(!) Необходимо использовать именно csript.exe.

WScript.Echo("Прием закончен...");
//обрабатываем запрос

Запрос от клиента будет иметь похожий вид:

GET / HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/x-ms-application, application/x-ms-xbap, application/vnd.ms-xpsdocument, application/xaml+xml, application/x-silverlight, application/msword, application/vnd.ms-powerpoint, application/vnd.ms-excel, */*
Referer: http://10.97.24.200:8080/page2.html
Accept-Language: ru
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 1.1.4322; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; InfoPath.1)
Host: 10.97.24.200:8080


Connection: Keep-Alive


Пробуем его обработать регулярными выражениями, с целью – узнать запрашиваемую страницу на сервере.

(!) Здесь показан только пример, для полноценной работы необходим более детальный анализ заголовка, что выходит за рамки этой статьи.

match1=data.match(/GET[\s]\/(.*?)[\s]*?HTTP\/?/i);

WScript.Echo("REGEXP:"+match1[1]);

В match1 получаем имя файла вида page.html.


//обрабатываем запрос (конец)

if(match1[1]=="")

{
fcontent=getfile("index.html");
}

else

{
fcontent=getfile(match1[1]);
}

Вызываем ф-ю чтения таргет-файла (которую опишем ниже), если результат пустой – переправляем на главную страницу сайта (Index.html).

size=fcontent.lenght; 

Здесь получаем размер возращаемой страницы, с целью вставки ее в заголовок ответа.

result="HTTP/1.0 200 OK\r\nServer: JscriptServer\r\nContent-Language: ru\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length:"+size+"\r\n\r\n";

result+=fcontent;

Формируем заголовок ответа. Опять же – Это Пример, на самом деле в зависимости от медиатипа файла нужно отдавать разные заголовки (Content-Type:{здесь вбивать медиатип согласно RFC} ). Будем считать что на нашем сервере хранится только текстовая информация, дописать для остальных медиатипов не сложно.

oWinsock.SendData(result );

Отсылаем данные клиенту.

oWinsock.CloseWinsock();

Закрываем коннект.

oWinsock = null;

Убиваем объект.

obInit();

Создаем объект заново, для выполнения последующего запроса.

}

Далее опишем ф-ю чтения запрашиваемого файла.

function getfile(file)
{
FSO= new ActiveXObject("Scripting.FileSystemObject");
try
{
fl=FSO.OpenTextFile(file,1);

resfile =fl.ReadAll();

fl=null;
}

catch(e)
{
if(e!=0)
{
fl=FSO.OpenTextFile("404.html",1);
resfile =fl.ReadAll();
}
}
return (resfile);
}

Замечу что просто чтение файла 404 страницы тут НЕ корректно, ибо надо менять в самом заголовке result="HTTP/1.0 200…

Остается только контроль ошибок и проверка соединения.

function oWinsock_Error(number, desc, sCode, src, help, helpctx, cancelDisplay)

{
WScript.Echo("Error: "+desc);
bClose=false;
}

function oWinsock_Close()

{
WScript.Echo("Connection closed.");
bClose=false;
}

obInit();
while(bClose)WScript.Sleep(1);

Чем меньше значение WScript.Sleep(1); тем чаще сервер будет обрабатывать запросы. В остальном вроде все понятно.

Осталось создать страницы в корне нашего сервера и запустить скрипт.

http server

В строке браузера набираем Http://127.0.0.1:8080 видим следующую картину:

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

Готовый скрипт можно скачать здесь.

Миграция на Windows 2003 Server с Novell Netware 6.5: Автоматизированный перенос пользователей.

Novell Netware одна из наиболее стабильных и надежных сетевых операционных систем, но имеет один неприятный недостаток - она разработана не в Microsoft, что влечет за собой стремительное сокращение оной на рынке серверных ОС. На сегодняшний день Novell не планирует развитие данной ОС, а стремительно (в 2008 году продано более 400 000 лицензий) выводит на рынок свои Linux дистрибутивы SLES и OES, что заставляет по крайней мере задуматься над миграцией на более перспективные современные ОС.

В этой статье я расскажу о переносе учетных записей пользователей с Novell Netware 6.5, на Windows 2003 Server путем импорта данных при помощи ODBC Driver for eDirectory и экспорта их при помощи пакетного файла Windows.


Пример не практический, но на основе его можно разработать перенос практически любой информации из базы Novel eDirectory в службу каталогов Active Directory.


Нам понадобиться:

  1. Сервер с установленным Windows 2003 Server с развернутым контроллером домена.
  2. Сервер с установленным Novell Netware 6.5 с заведенными пользователями и группами.
  3. Рабочая станция (OS Windows) с установленным клиентом Novell Netware, PHP5 и ODBC Driver for eDirectory.

Этап #1: Создание DSN Novell ODBC for NDS

ODBC Driver for eDirectory универсальный программный интерфейс, для доступа к базе Novell eDirectory, позволяющий обращаться к ней различным программным средствам (например MS Access, MS Excel, Vba и многим другим) с использованием SQL синтаксиса, незная внутреннего устройства eDirectory. ODBC позволяет значительно снизить время разработки ПО, но в то же время не расчитан на большое число подключений клиентов.

Для того чтобы установить ODBC Driver надо либо скачать его с сайта Novel, либо если у вас установлена ConsoleOne в папке *\consoleone\1.2\reporting\bin\odbc.exe. После установки необходимо настроить источник данных, для чего запустить Администратор источников данных ODBC. - odbcad32.exe. Далее перейти на вкладку Системный DSN и нажать кнопку Добавить, в следующем списке выбрать Novell ODBC Driver for NDS.



NDS - так называлось то что впоследствии переросло в eDirectory.


Далее ввести имя источников данных и обозначить дерево, имя не важно какое, но мы введем Novell.

Жмем два раза ок.

Этап #2: Извлекаем данные Novell eDirectory.

Для подключения к источникам данных ODBC будем использовать язык программирования PHP5. Итак сам код.

Объявляем переменные:

Имя домена первого и второго уровня
//Имя домена
$dc1="local";
$dc2="dc";
$context='.imns';
На следующих двух ф-ях заострять внимание пока не следует.

Вспомогательная функция для вывода на консоль в ДОС кодировке:
function echo_con($str)// вывод в консоль в дос кодировке
{
$str=convert_cyr_string($str,'w','a');
echo($str."\n");
return($str);
}
Функция для записи аварийных ситуаций
function error_con($err_dump)  // ф-я записи аварийных ситуаций
{
//Запись ошибок в лог:
$hd1=fopen("CONECT_ERROR.log", "w");
fwrite($hd1,$err_dump);
fclose($hd1);
}
Подключаемся к базе данных.

Обращаю внимание что в ф-ии odbc_connect() не обязательно вводить логин и пароль, если вы залогинены под админом, а это лучше сделать.
//***ПОДКЛЮЧАЕМСЯ К ОРАКЛУ****
echo_con("Подключаемся к базе, ждите..");

$con=odbc_connect("novell", "admin", "Очень_сложный_пароль",'SQL_CURSOR_USE_ODBC'); //УБРАТЬ ПАРОЛ�� !!!!
if ($con==0)
{
echo "Error Connect to server";
$err_dump.=odbc_error($con);

error_con($err_dump);
echo_con("/n ОШШИБКА СОЕДИНЕНИЯ С СЕРВЕРОМ!") ;
exit;
}
echo "OK... $con \n";

Тепрерь мы можем сформировать и выполнить запрос к базе eDirectory используя SQL синтаксис
Здесь мы выполняем запрос к таблице UserNDS на выборку имени пользователя в eDirectory, имени, фамилии, дерева, полного имени, группам в которых состоит пользователь, Id пользователя и емейла.

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

//Подготавливаем и выполняем запрос
echo_con("Ждите. Выполняем выборку из базы...");

$stmt = odbc_exec ($con,'SELECT "Given Name", "Surname", "NDS_Tree", "Full Name", "Group Membership", "GUID_Data","EMail Address_Addr", "CN"

FROM UserNDS

' );
Далее переводим полученные данные в два массива, первый - содержит все выбранные группы пользователей, второй- содержит всю информацию о пользователях.
$i=0; //для подсчета итераций в цикле
// присваеваем выходящему массиву значения из результата запроса
while(odbc_fetch_row($stmt))
{
if (odbc_result($stmt, 6)==$flagGUID_data)
{
if (trim(odbc_result($stmt, 5))!=='')
{
$groupArr[]=str_replace($context,'',trim(odbc_result($stmt, 5)));
}
$i--;
$out_arr[$i]['Group_Membership'] = $out_arr[$i]['Group_Membership']."|".str_replace($context,'',trim(odbc_result($stmt, 5)));
}
else
{
echo_con("User #: ".$i."\n");
echo_con("Given Name: ".$out_arr[$i]['Given_Name'] = trim(odbc_result($stmt, 1)));
echo_con("Surname: ".$out_arr[$i]['Surname'] = trim(odbc_result($stmt, 2)));
echo_con("Full Name: ".$out_arr[$i]['Full_Name'] = trim(odbc_result($stmt, 4)));
echo_con("Email: ".$out_arr[$i]['EMail Address_Addr'] = trim(odbc_result($stmt, 7)));
echo_con("Login: ".$out_arr[$i]['CN'] = trim(odbc_result($stmt, 8)));
echo_con("GUID_Data: ".$out_arr[$i]['GUID_Data'] = trim(odbc_result($stmt, 6)));
echo_con("Group Membership: ".$out_arr[$i]['Group_Membership'] = str_replace($context,'',trim(odbc_result($stmt, 5))));

$flagGUID_data= odbc_result($stmt, 6);
if (trim(odbc_result($stmt, 5))!=='')
{
$groupArr[]=str_replace($context,'',trim(odbc_result($stmt, 5)));
}
}
$i++;
}
В итоге имеем два массива данных, достаточных для выполнения нашей задачи.

Этап #3: Формируем пакетный файл для экспорта данных в eDirectory

Следующий код будет формировать bat файл, в котором используеться консольная утилита DSADD выполняющая добавление новых объектов в домен AD.

Почему именно бат файл? Почему не внести данные в базу AD прямо из скрипта?

Дело в том, что в Novell eDirectory есть несколько объектов (в том числе и users) являющихся системными, и переносить их в AD в подавляющем большинстве случаев не требуется. Понятно что такие объекты отследить программно сложно, поэтому и генерируем bat-ник, который потом может быть подредактирован системным администратором.
Далее код генерирующий пакетный файл.
//****Создаем cmd код для групп****
$batGroupUser.="
@ECHO OFF
rem Батник для создания пользователей и групп в домене
rem 1. Создаем группы.\n";
foreach ($groupArr as $val)
{
$batGroupUser.="dsadd group \"CN=$val"."1".",OU=test,DC=$dc2,DC=$dc1\"^
-secgrp yes^
-scope g^
-samid $val"."1"."^
-desc \"Novell_migrate\"\n Pause \n";
}
$batGroupUser.="\n\nrem 2. Создаем пользователей.\n";
foreach($out_arr as $val_u)
{
if ($val_u['CN'])
{
$batGroupUser.="\n"."dsadd user cn=\"$val_u[CN]\",ou=test,dc=$dc2,dc=$dc1^
-samID $val_u[CN]^\n";
if ($val_u['Given_Name'])
{
$batGroupUser.=" -fn \"$val_u[Given_Name]\"^\n";
}
if ($val_u['Surname'])
{
$batGroupUser.=" -ln \"$val_u[Surname]\"^\n";
}
if ($val_u['EMail Address_Addr'])
{
$batGroupUser.=" -email \"".$val_u['EMail Address_Addr']."\"^\n";
}
if ($val_u['Group_Membership'])
{ $batGroupUser.=" -memberOf";
$memb_arr=explode("|",$val_u['Group_Membership']);
foreach($memb_arr as $val_memb)
{
$batGroupUser.=" cn=\"$val_memb\",ou=test,dc=$dc2,dc=$dc1";
}
$batGroupUser.="^\n";
}
$batGroupUser.=" -pwd PassWord42^"."\n"." -desc \"Netware Migrate User\"\n Pause \n";
}
}
$batGroupUser.= "\n Pause";
$batHd=fopen("NetwareToWindows2003Server.bat",'w');
fwrite($batHd,$batGroupUser);
echo "END";

die();
В результате получаем пакетный файл, который после изучения и редактирования запускаем на Windows 2003 Server.