скрыть

скрыть

  Форум  

Delphi FAQ - Часто задаваемые вопросы

| Базы данных | Графика и Игры | Интернет и Сети | Компоненты и Классы | Мультимедиа |
| ОС и Железо | Программа и Интерфейс | Рабочий стол | Синтаксис | Технологии | Файловая система |



Google  
 

Понимание много-поточности в VCL для веб-серверных ISAPI-расширений



Поймал Иван-дурак в проруби щуку. Та ему:
- Отпусти ты меня, Иван, и любое твое желание по-щучьму велению, по твоему хотению будет исполнено! Обрадовался Ванюха, кинул щуку обратно в прорубь и говорит:
- Хочу знать, не слезая с печи, все что в мире творится за лесами-за горами, за морями-окиянами. Хочу под музыку балдеть, на голых девок день и ночь пялиться, с заморскими дураками переписываться и все новые анекдоты про Царя-батюшку первому в мире узнавать! Так Иван-дурак стал первым на Руси пользователем Интернета.

В среде Delphi можно создавать высокоэффективные веб-серверные ISAPI-расширения на основе технологии WebBroker. Создайте проект с помощью мастера (New -> Web Server Application - ISAPI DLL). Прилагаемая справочная документация, а так же демонстрационный пример "$(DELPHI)\Demos\Webserv" позволяют достаточно быстро освоиться в приемах написания веб-серверных ISAPI-расширений. На выходе у вас получится обычная DLL (далее по тексту - библиотека).

Сложность заключается в том, что веб-сервер (для ускорения обработки поступающих запросов) вызывает нашу библиотеку в много-поточном режиме. В результате чего на разработчика ложиться ответственность за написание поточно-безопасного кода. Не беспокойтесь, ребята из Borland постарались упростить вам жизнь настолько, насколько это возможно. Когда я понял смысл "обертки" TWebApplication и наследника TISAPIApplication, то был восхищен, и вдохновлен поделиться этими знаниями с вами!

Согласно спецификации ISAPI-расширений, созданная библиотека имеет всего три экспортируемые функции: GetExtensionVersion, HttpExtensionProc, TerminateExtension. Нас интересует только HttpExtensionProc, через которую выполняется вся работа: получение запросов с веб-сервера (Request), обработка и обратная отправка результата (Response).

Итак, рассмотрим весь путь прохождения данных. Запрос веб-сервера поступает через экспортируемую библиотекой функцию HttpExtensionProc в TISAPIApplication через инкапсулированный метод с одноименным названием (объект Application, как и в любом VCL-приложении другого вида, присутствует всегда: создается при инициализации и разрушается при завершении приложения, однако в данном случае имеет тип TISAPIApplication):


function TISAPIApplication.HttpExtensionProc
(var ECB: TEXTENSION_CONTROL_BLOCK): DWORD;
var
  HTTPRequest: TISAPIRequest;
  HTTPResponse: TISAPIResponse;
begin
  try
    HTTPRequest := NewRequest(ECB);
    try
      HTTPResponse := NewResponse(HTTPRequest);
      try
        if HandleRequest(HTTPRequest, HTTPResponse) then
          Result := HSE_STATUS_SUCCESS
        else
          Result := HSE_STATUS_ERROR;
      finally
        HTTPResponse.Free;
      end;
    finally
      HTTPRequest.Free;
    end;
  except
    HandleServerException(Exception(ExceptObject), ECB);
    Result := HSE_STATUS_ERROR;
  end;
end;

Из приведенного кода видно, что переменные HTTPRequest и HTTPResponse объявлены локально, и объекты соответствующих типов создаются для каждого поступающего запроса веб-сервера. После инициализации этих переменных обработка переходит к TWebApplication.HandleRequest:


function TWebApplication.HandleRequest(Request: TWebRequest;
Response: TWebResponse): Boolean;
var
  DataModule: TDataModule;
  Dispatcher: TCustomWebDispatcher;
  I: Integer;
begin
  Result := False;
  DataModule := ActivateWebModule;
  if DataModule <> nil then
    try
      if DataModule is TCustomWebDispatcher then
        Dispatcher := TCustomWebDispatcher(DataModule)
      else
        with DataModule do
        begin
          Dispatcher := nil;
          for I := 0 to ComponentCount - 1 do
          begin
            if Components[I] is TCustomWebDispatcher then
            begin
              Dispatcher := TCustomWebDispatcher(Components[I]);
              Break;
            end;
          end;
        end;
      if Dispatcher <> nil then
      begin
        Result := TWebDispatcherAccess(Dispatcher).DispatchAction(Request, Response);
        if Result and not Response.Sent then
          Response.SendResponse;
      end
      else
        raise Exception.CreateRes(@sNoDispatcherComponent);
    finally
      DeactivateWebModule(DataModule);
    end;
end;

Тут следующая хитрость: локально объявленная переменная DataModule получает ссылку на объект от метода TWebApplication.ActivateWebModule. Для каждого потока предоставляется неиспользуемый в настоящее время другими потоками объект типа TDataModule, для чего выполняется перемещение этих объектов между списками FInactiveWebModules и FActiveWebModules. Если список FInactiveWebModules исчерпан, то создается новый экземпляр объекта типа TDataModule. В результате этих манипуляций для каждого потока используется собственный экземпляр объекта типа TDataModule, и разработчик может быть уверен в поточно-безопасном объявлении полей данных своего объекта TWebModule! Но это еще не все.

Локально объявленные в TISAPIApplication.HttpExtensionProc переменные HTTPRequest и HTTPResponse, о которых говорилось выше, переданы методу TWebApplication.HandleRequest в качестве параметров Request и Response, который в свою очередь передает их методу TCustomWebDispatcher.DispatchAction:


function TCustomWebDispatcher.DispatchAction(Request: TWebRequest;
Response: TWebResponse): Boolean;
var
  I: Integer;
  Action, default: TWebActionItem;
  Dispatch: IWebDispatch;
begin
  FRequest := Request;
  FResponse := Response;
  {...}
end;

Тут выполняется присваивание переменных Request и Response полям объекта TWebModule (как наследнику TCustomWebDispatcher). А нам уже известно, что экземпляр объекта TWebModule у каждого потока - собственный. Теперь посмотрим правде в глаза: у каждого запроса веб-сервера есть собственные экземпляры объектов TRequest и TResponse в полях TWebModule.Request и TWebModule.Response; и они поточно-безопасны.

Далее путь лежит через метод TWebActionItem.DispatchAction, который вызывается в TCustomWebDispatcher.DispatchAction. Тут может вступать в действие ваш код обработки запроса, после чего подготовленному ответу предстоит обратная дорога.

Как видно из приведенного выше фрагмента кода TWebApplication.HandleRequest - DataModule передается в качестве параметра методу TWebApplication.DeactivateWebModule, в котором может быть переведен в список FInactiveWebModules, или вовсе разрушен (если выключено свойство CacheConnections - этим не стоит пользоваться без необходимости, так как существенно снижается производительность обработки запросов). После чего обработка возвращается к TISAPIApplication.HttpExtensionProc и ответ передается веб-серверу вызовом Response.SendResponse.

Отдельно следует отметить. Мне несколько раз попадались на глаза рекомендации устанавливать глобальную переменную IsMultiThread к True в dpr-файл проекта - этого делать не нужно, т.к. в конструкторе TWebApplication эта работа уже выполняется!

Если вы используете доступ к BDE посредством наследников TBDEDataSet (TTable, TQuery, TStoredProc) то все что вам нужно сделать для обеспечения поточно-безопасности, это присвоить в конструкторе TWebModule: Session.AutoSessionName := True (подробнее смотри в справочной документации: "Managing multiple sessions").

Реализация инкапсуляции WinSock в компонентах TClientSocket и TServerSocket, которые вам могут потребоваться, так же поточно-безопасна.

Конечно, если используется файловый ввод-вывод, а так же прямые вызовы WinSock, то тогда все же нужно выполнять много-поточную защиту самостоятельно и вам все же придется прочитать раздел документации "Programming with Delphi - Using threads". :-)

Замечание: изложенное выше относится к Delphi 5.






Copyright © 2004-2016 "Delphi Sources". Delphi World FAQ




Группа ВКонтакте   Ссылка на Twitter   Группа на Facebook