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 типы, кеширование, многопоточность, поддержку динамических страниц и т д..

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

1 комментарий:

Евгений Бурдаков комментирует...

Можно ли привести пример клиента, а не сервера? Мне необходимо наоборот послать запрос (определенно сформированный пакет данных) на устройство на заданный IP и порт и получить ответ.

Отправить комментарий