| web-библиотека, если угодно - web-платформа |
[Apr. 25th, 2008|08:52 pm] |
Я тут приуготавливаюсь сделать небольшой прорыв в web-технологиях :) Ну, во-первых мне это и самому интересно, во-фторых, я, как заядлый морфинист дельфинист вознамерился сделать нечто такое, чтобы оно с одной стороны было на Borland Delphi, а с другой - с полным, так сказать, функционалом, сопоставимым с серьёзными web-библиотеками. Для начала я (естественно!) кинулся на WebSnap (BD7), и... дада, сия библиотека меня расово не порадовала ну никак, по следующим причинам:
- Глючность. Не буду сейчас перечислять все еполадки, скажу что их достаточно для расового отвращения.
- Неполнота. Например, у них определён интерпейс IImageProducer, но реализации его НЕТЪ (а использование есть). Пришлось писать самому, это совсем не сложно было - но вот осадочек-с остался.
- Слабая портируемость на Kylix (ну... не то чтобы это сов7 нельзя, но как-то муторно, например все датамодули переделывать, ибо то же ADO или BDE в Линуксе не поддерживаются - можно, конечно, DBExpress, но оно не пошло у меня как то с MySQL, то ли у меня руки кривые, то ли MySQL5 оно поддерживать не хочет)
- Отсутствие модульности. Всё "сидит" в одной dll-ке (или одном exe-шничке), то есть если уж перекомпиляем - то фсё и зараз.
- ТОРМОЗА! Я вот только один пример приведу - страничка с 50-ю итерациями на скрипте в ней, генерится 0.5 секунды. А что Вы хотите? JavaScript + OLE Automation. Ффффторой ASP, ога.
- Какие-то глюки при множестве параллельных запросов. Возможно, по причине предыдущего пункта.
В общем, не обрадованъ ни разу. Хоть и написал уже на WebSnap-e почти действующий форум, по мотивам phpBB - но решил расово слить его в унитаз, а опосля разобрать на куски, если пойдут другие наработки. И они воспоследовали. Решил таки интересу ради и развития для написать свою собственную web-библиотеку (которая начала стремительно приобретать черты web-платформы), с кодовым названием DWE (Delphi Web Engine - бесхитростно, ога). Что для неё характерно в первую очередь?
- Изначальная кроссплатформенность (пишется на CLX, а не VCL) - не, ну местами, конечно приходится пользоваться непортируемым кодом. Но это дело я к минимуму свести стараюсь.
- Модульность. Сама по себе DWE - это только система управления модулями, что-то полезное делают отдельные DLL-ки, подгружаемые динамически.
- Компилируемость. А вот это совсем интересно - страничка со встроенными в ней скриптами в конечном итоге становится DLL-кой, которая и выдаёт результирующую HTMK.
- Объектность. Там всё сделано через объекты, очень такая серьёзная иерархия.
В DWE сейчас есть такие вот "фичи":
- Все данные представлены как вариации интерпейса IDWEValue, если объект поддерживает дополнительные интерпейсы - значит его данные можно представить как данные этого, описанного интерпейсом типа. Один и тот же объект может поддерживать несколько интерпейсов. В настоящее время поддерживаются следующие интерпейсы:
- IDWEValue - базовый интерфейс, поддерживает проверку поддержки дополнительных интерфейсов и удобное получение этих интерфейсов. Например, так
Obj1.AsInteger.Value := ... Также поддерживает "подзначения" (значения типа IDWEValue, "принадлежащие" данному, по символическому имени) - но сам по себе объект может и не поддерживать "подзначений", тогда считается что у него их просто нетъ. Так вот, так сказать от 120 единиц и имеем "иерархию" объектов, к каждому из них можно обратиться через нотацию с точкою, например так Obj1['Obj2.Obj2b.Obj3'].AsDouble := ...
- IDWEVariantValue - значение типа Variant
- IDWEIntegerValue - значение типа Integer
- IDWEInt64Value - значение типа Int64
- IDWEDoubleValue - значение типа Double (с плавающей точкою)
- IDWEAnsiStringValue, IDWEWideStringValue, IDWEStringValue - строки в разных вариациях, а именно - без Unicode, с Unicode и принятые в системе "по умолчанию"
- IDWEDateTimeValue - значение типа дата-время, поддерживает обращение по отдельности к полям "год", "месяц", "час", "секунда" - ну и в том же духе.
- IDWEBooleanValue - булевское значение
- IDWEHandleValue - значение типа THandle
- IDWEMethod - значение типа "метод", в нём есть метод (простите за тавтологию) Invoke, получающий в качестве параметра объект типа IDWEValue, возвращающий в результате объект того же типа. Объект может из себя представлять иерархию, поэтому достаточно и одного параметра на вход, одного - на выход. "Стандартная" реализация этого интерфейса сделана через Delphi-event (callback по другому), но может быть реализовано и как разбор какого-нибудь скрипта, который лежит в том же объекте и к которому можно иметь доступ через интерпейс IDWEStringValue. Как-то так.
- IDWEArray - одномерный массив значений, работает одновременно и как список (с методами Next, Prev и т.д.), индексация массива - через целочисленный индекс.
- IDWEList - одномерный список значений, не поддерживает индексацию - доступ только через движение по списку и взятие "текущего" значения.
- IDWEStringList - "оболочка" для стандартного объекта TStringList (который вообще-то и как массив работает), полностью дублирует все его методы и свойства. Но вместо хранения со строкою опционального объекта, порождённого от TObject, хранит значения типа IDWEValue.
- IDWEStream - поток для досупа к данным (например, к файлам) - в общем, ввод-вывод. Яхвляется оболочкой для стандартного объекта TStream, но поддерживает ещё кое-что интересное, например хранение строк переменной длины, или хранение "упакованных" целочисленных значений (пакуются с расчётом на то, что они малы по величине - это для хранения, допустим, индексов в массивах, не очень больших).
- IDWEMemory - интерфейс прямого досупа к памяти объекта. Может и пригодится когда-нибудь.
- IDWEClass - т.н. "класс", обладающий заранее заданным и неизменным набором "подзначений", которые задаются в объекте типа IDWEClassTemplate (со значениями по умолчанию), на который у "класса" имеется ссылочка как на "шаблон".
- IDWEClassTemplate - "шаблон" класса, может быть "порождён" от другого шаблона, тогда шаблон наследует все "подзначения" своего родителя (и родителя родителя, и так далее...)
- IDWEBuffer - поддержка буферизации, есть методы Flush (актуализовать данные в некоем носителе) и Cancel (вернуться к значению, которе лежит в носителе в данный момент)
- IDWEWebEngine - самый главный "родительский" объект, поддерживает подключемые модули, генерацию html-страниц и всё такое
- IDWEModule - подключаемый модуль, поддерживает создание "шаблонов объектов" и порождение объектов от шаблонов, может иметь также "методы" (IDWEMethod) и прочие другие всякие подзначения, по вкусу.
- IDWEFile - объект-файл, поддерживает чтение даты модификации файла, узнавание его наличия, стирание существующего и тд.тп. Также оно и развитие интерпейса IDWEStream, только пердварительно его надобно открыть методом "Open".
- IDWEStore - объект, поддерживающий хранение его в каком-либо промеж уточном формате, то есть его можно куда-то сохранить, и откуда-то прочитать.
- IDWEStorage - объект, поддерживающий хранение объектов, поддерживающих интерпейс IDWEStore. В настоящее время объекты можно хранить в бинарном формате (в IDWEStream) или в текстовом (IDWEStringList). При желании можно сделать возможным хранение объекта, допустим, в базе данных.
- IDWELink - "ссылка" на другой объект, может быть сохранена в промежуточном хранилище - но для этого и объект-ссылка, и объект, на который ссылаются должны принадлежать общему "родителю" (а по идее, все должны принадлежать объекту типа IDWEWebEngine)
- IDWEGroup - "группа значений", вот этот объект поддерживает "подзначения", которые можно свободно изменять, удалять, добавлять и ссылатья на них по строковому имени. В свою очередь, подзначения могут быть сами группами (и это, так сказать, система, работающая от 120 единиц). Классы и шаблоны классов - "потомки" группы, с некоторыми своими изменениями, в частности, в классах нельзя удалять и добавлять новые значения (ибо пользователь класса должен знать, что все значения присутствуют как есть и тип их гарантирован).
- IDWELock - объект для синхронизации потоков (thread-ов), через него реализуются секции кода, которые не могут выполняться одновременно. Семафоры, одним словом, если по юниксоидному.
- IDWEConfig - объект, примерно реализующий функциональность ini-файлов
- IDWEWriteLock - объект опять-таки для синхронизации потоков, на этот раз с учётом того, каков требуется доступ - на чтение или на запись. Параллельное чтение допускается, чтение параллельно с записью или параллельные записи - нет.
- IDWEDocument - объект-генератор html-кода, это "потомок" интерфейса IDWEModule, но он умеет ещё и html-код генерить, да ещё и с поддержкой "контекста" (параметров генерации, которые можно задать перед генерацией), таким образом одна и та же страничка может быть сгенерена по-разному, например с разным SQL-запросом, всё это задаётся в "контексте" (который есть объект типа IDWEClass)
- IDWEWebContext - объект, реализующий хранение веб-контекста, как то запрос, ответ, и прочая такая информация.
- IDWEConnection - объект, реализующий некое "абстрактное" подключение к чему-либо (открытие файла, таблицы в БД, соединения TCP/IP и так далее)
- IDWESQLConnection - SQL-соединение к базе данных, в нём можно выполнять запросы и получать от него объекты-DataSet-ы. Порождён от IDWEConnection. В настоящее время поддерживаются ADO-подключения (как отдельный подгружаемый модуль), поддерживаются но не оттестированы :)
- IDWEDBSet - объект-набор данных (DataSet) из БД. Например, таблица или результаты SQL-запроса. Позволяет примерно то же, что и стаднартный TDataSet в делфях.
- Генерация страничек делается следующим образом:
- Для начала проверяется, есть ли уже в наличие DLL-ка, которая генерит.
- Если её нет, либо дата её создания раньше даты модификации исходного файла скрипта, либо дата некоторых важных библиотек позже её даты (то есть перекомпилена была сама DWE) - то создаём её - сначала делаем *.pas файл, содержащий Delphi-код (а он создаётся с учётом содержащегося в файле-исходнике скрипта вперемешку с HTML-кодом). Затем компилим её компилятором "командной строки" dcc32.exe.
- Подгружаем эту dll-ку, создаём через вызов её функции объект типа IDWEModule и регистрим его унутри себя. Чтобы другие уже могли им пользоваться (сославшись на него по текстовому имени).
- Текстовый формат хранения иерархии объектов, ну примерно таков:
group = {
A:integer = -1
B:integer = 5
C:double = 2,5
D:string = AAA
E:boolean = true
#desc = some desc
F:group = {
F1:datetime = 25.04.2008 20:38:55
F2:string_list = {
store_values = true
AAA
empty
BBB
empty
CCC
integer = 0
}
}
G:variant = currency:3,5
H:array = {
low_bound = 0
element_type = integer
integer = 1
integer = 2
integer = 3
integer = 4
integer = 5
}
I:link = F.F1
J:datetime = 25.04.2008 20:38:55
K:lock
L:class_template = {
_Parent:link
_ClassName:string
}
}
- Исходники тестовой dll-ки, сгенерёной из html-шаблона таковы:
- content\test.html
Params.AddSubValue('Date', CreateDateTimeValue);
Params.AddSubValue('Int', CreateIntegerValue);
<!Content>
<html>
<body>
<!Code>
Context['Date'].AsDateTime.Value:=Now;
Context['Int'].AsInteger.Value:=Random(500);
Engine.LoadConfig('test.cfg');
<!End>
Date = <%= Date %>
Int = <%= Int %>
</body>
</html>
- bin\test.pas
library test;
uses
ShareMem, SysUtils, Classes, HTTPApp, IniFiles, Math, DateUtils, StrUtils,
web_interface;
type
TDWEModule_test = class(TDWEDocument)
public
function Generate(WebContext : IDWEWebContext; Context : IDWEClass) : IDWEStringList;override;
end;
const
ModuleClassName = 'TDWEModule_test';
ModuleName = 'test';
function ModuleFunc(WebEngine : IDWEWebEngine) : IDWEModule;stdcall;
var
Module : IDWEModule;
Params : IDWEClassTemplateEditor;
begin
Module:=TDWEModule_test.Create(WebEngine, 'test');
Params:=Module.CreateTemplate(nil, '_Params');
Params.AddSubValue('Date', CreateDateTimeValue);
Params.AddSubValue('Int', CreateIntegerValue);
Params.MarkReadonly;
Module.DoneInitialization;
Result:=Module;
end;
function TDWEModule_test.Generate;
var
Content : IDWEStringList;
procedure Write(S : String);
var LS : String;
begin
if not Content.EOL then
begin
LS:=Content.Strings[Content.HighBound];
LS:=LS+S;
Content.Strings[Content.HighBound]:=LS;
end else
begin
Content.Add(S);
end;
end;
procedure Writeln(S : String);
begin
Write(S);
Content.Add('');
end;
begin
Content:=CreateStringList;
Content.StoreValues:=false;
Writeln('<html>');
Writeln('<body>');
Context['Date'].AsDateTime.Value:=Now;
Context['Int'].AsInteger.Value:=Random(500);
Engine.LoadConfig('test.cfg');
Writeln('Date = '+Context['Date'].AsString.Value+'');
Writeln('Int = '+Context['Int'].AsString.Value+'');
Writeln('</body>');
Writeln('</html>');
Result:=Content;
end;
exports
ModuleFunc name 'ModuleFunc';
begin
Randomize;
end.
Гойспода программисты - Вываше мнение?
|
|
|