Delphi Web Server 流程分析

通过向导 "Web Server Application" (选择 "Stand-alone GUI Application") 创建一个 WebServer Demo。

主单元代码:

......

  private
    FServer: TIdHTTPWebBrokerBridge;
    procedure StartServer;

.............

Delphi的网络组件是基于INDY的,这里的FServer是TIdHTTPWebBrokerBridge,就是WebServer了。是什么呢?

直接"Ctrl+Click"浏览代码,直接报错:

哎........

手动打开单元 "IdHTTPWebBrokerBridge.pas"(路径:"D:\Program Files (x86)\Embarcadero\Studio\22.0\source\Indy10\Protocols"):

TIdHTTPWebBrokerBridge = class(TIdCustomHTTPServer),

先看看继承:

TIdHTTPWebBrokerBridge->TIdCustomHTTPServer->TIdCustomTCPServer->TIdComponent...

是从TCPServer来的。

再看主单元的StartServer方法,是如何启动Server的:

procedure TForm1.StartServer;
begin
  if not FServer.Active then
  begin
    FServer.Bindings.Clear;
    FServer.DefaultPort := StrToInt(EditPort.Text);
    FServer.Active := True;
  end;
end;

设置Active=True启动,(这里可以"Ctrl+Click" "Active"属性),直接到"IdCustomTCPServer.pas"的 " TIdCustomTCPServer"类,看代码:

SetActive -> Startup():


procedure TIdCustomTCPServer.Startup;
var
  LScheduler: TIdScheduler;
  LIOHandler: TIdServerIOHandler;
  {$IFDEF CanCreateTwoBindings}
  LBinding: TIdSocketHandle;
  {$ENDIF}
begin
  // Set up bindings
  if Bindings.Count = 0 then begin
                                                                        
    // Binding object that supports both IPv4 and IPv6 on the same socket...

    {$IFDEF CanCreateTwoBindings}LBinding := {$ENDIF}Bindings.Add; // IPv4 or IPv6 by default

    {$IFDEF CanCreateTwoBindings}
                                                                                  
    case LBinding.IPVersion of
      Id_IPv4: begin
        if GStack.SupportsIPv6 then begin
          Bindings.Add.IPVersion := Id_IPv6;
        end;
      end;
      Id_IPv6: begin
        if GStack.SupportsIPv4 then begin
          Bindings.Add.IPVersion := Id_IPv4;
        end;
      end;
    end;
    {$ENDIF}
  end;

  // Setup IOHandler
  LIOHandler := FIOHandler;
  if not Assigned(LIOHandler) then begin
    LIOHandler := TIdServerIOHandlerStack.Create(Self);
    SetIOHandler(LIOHandler);
    FImplicitIOHandler := True;
  end;
  LIOHandler.Init;

  // Set up scheduler
  LScheduler := FScheduler;
  if not Assigned(FScheduler) then begin
    LScheduler := TIdSchedulerOfThreadDefault.Create(Self);
    SetScheduler(LScheduler);
    FImplicitScheduler := True;
    // Useful in debugging and for thread names
    LScheduler.Name := Name + 'Scheduler';   {do not localize}
  end;
  LScheduler.Init;

  StartListening;
end;

这里添加了ip6支持,然后设置IO句柄(用于https,加密,压缩等),然后是设置Scheduler(用于设置线程或纤程)。然后开始监听端口StartListening(),StartListening()主要是检查监听线程数量和创建监听线程,默认监听线程数量=15:

const
  IdListenQueueDefault = 15;

procedure TIdCustomTCPServer.InitComponent;

..........

FListenQueue := IdListenQueueDefault;

..................

这个数量太小了,现在的一般台式机都可以轻松支持200以上,所以建议开始时设置,

procedure TForm1.FormCreate(Sender: TObject);
begin
  FServer := TIdHTTPWebBrokerBridge.Create(Self);
  FServer.ListenQueue := 200;
end;

StartListening():

procedure TIdCustomTCPServer.StartListening;
var
  LListenerThreads: TIdListenerList;
  LListenerThread: TIdListenerThread;
  I: Integer;
  LBinding: TIdSocketHandle;
  LName: string;
begin
  LListenerThreads := FListenerThreads.LockList;
  try
    // Set up any sockets that are not already listening
    I := LListenerThreads.Count;
    try
      while I < Bindings.Count do begin
        LBinding := Bindings[I];
        LBinding.AllocateSocket;
        // do not overwrite if the default. This allows ReuseSocket to be set per binding
        if FReuseSocket <> rsOSDependent then begin
          LBinding.ReuseSocket := FReuseSocket;
        end;
        DoBeforeBind(LBinding);
        LBinding.Bind;
        LBinding.UseNagle := FUseNagle;
        Inc(I);
      end;
    except
      Dec(I); // the one that failed doesn't need to be closed
      while I >= 0 do begin
        Bindings[I].CloseSocket;
        Dec(I);
      end;
      raise;
    end;

    if I > LListenerThreads.Count then begin
      DoAfterBind;
    end;

    // Set up any threads that are not already running

    LName := Name;
    if LName = '' then begin
      LName := 'IdCustomTCPServer'; {do not localize}
    end;

    for I := LListenerThreads.Count to Bindings.Count - 1 do
    begin
      LBinding := Bindings[I];
      LBinding.Listen(FListenQueue);
      LListenerThread := TIdListenerThread.Create(Self, LBinding);
      try
        LListenerThread.Name := LName + ' Listener #' + IntToStr(I + 1); {do not localize}
        LListenerThread.OnBeforeRun := DoBeforeListenerRun;
                                                            
        //http://www.midnightbeach.com/jon/pubs/2002/BorCon.London/Sidebar.3.html
        LListenerThread.Priority := tpListener;
        LListenerThreads.Add(LListenerThread);
      except
        LBinding.CloseSocket;
        FreeAndNil(LListenerThread);
        raise;
      end;
      LListenerThread.Start;
    end;
  finally
    FListenerThreads.UnlockList;
  end;
end;

设置完线程,然后启动线程,就开始端口监听了,工作就转到了监听线程TIdListenerThread:

TIdListenerThread = class(TIdThread)

父类TIdThread处理一般情况,子类只要实现Run()抽象方法:


procedure TIdListenerThread.Run;
var
  LContext: TIdServerContext;
  LIOHandler: TIdIOHandler;
  LPeer: TIdTCPConnection;
  LYarn: TIdYarn;
begin
  Assert(Server<>nil);
  Assert(Server.IOHandler<>nil);

  LContext := nil;
  LPeer := nil;
  LYarn := nil;
  try
    // GetYarn can raise exceptions
    LYarn := Server.Scheduler.AcquireYarn;

                                                                       
    // the user to reject connections before they are accepted.  Somehow
    // expose an event here for the user to decide with...
    
    LIOHandler := Server.IOHandler.Accept(Binding, Self, LYarn);
    if LIOHandler = nil then begin
      // Listening has finished
      Stop;
      Abort;
    end else begin
      // We have accepted the connection and need to handle it
      LPeer := TIdTCPConnection.Create(nil);
      {$IFDEF USE_OBJECT_ARC}
      // under ARC, the TIdTCPConnection.IOHandler property is a weak reference.
      // TIdServerIOHandler.Accept() returns an IOHandler with no Owner assigned,
      // so lets make the TIdTCPConnection become the Owner in order to keep the
      // IOHandler alive when this method exits.
      //
                                                                           
      //
      LPeer.InsertComponent(LIOHandler);
      {$ENDIF}
      LPeer.IOHandler := LIOHandler;
      LPeer.ManagedIOHandler := True;
    end;

    // LastRcvTimeStamp := Now;  // Added for session timeout support
    // ProcessingTimeout := False;

    // Check MaxConnections
    if (Server.MaxConnections > 0) and (not Server.Contexts.IsCountLessThan(Server.MaxConnections)) then begin
      FServer.DoMaxConnectionsExceeded(LIOHandler);
      LPeer.Disconnect;
      Abort;
    end;

    // Create and init context
    LContext := Server.FContextClass.Create(LPeer, LYarn, Server.Contexts);
    LContext.FServer := Server;
    // We set these instead of having the context call them directly
    // because they are protected methods. Also its good to keep
    // Context indepent of the server as well.
    LContext.OnBeforeRun := Server.ContextConnected;
    LContext.OnRun := Server.DoExecute;
    LContext.OnAfterRun := Server.ContextDisconnected;
    LContext.OnException := Server.DoException;
    //
    Server.ContextCreated(LContext);
    //
    // If all ok, lets start the yarn
    Server.Scheduler.StartYarn(LYarn, LContext);
  except
    on E: Exception do begin
      // RLebeau 1/11/07: TIdContext owns the Peer by default so
      // take away ownership here so the Peer is not freed twice
      if LContext <> nil then begin
        TIdServerContextAccess(LContext).FOwnsConnection := False;
      end;
      FreeAndNil(LContext);
      FreeAndNil(LPeer);
      // Must terminate - likely has not started yet
      if LYarn <> nil then begin
        Server.Scheduler.TerminateYarn(LYarn);
      end;
      // EAbort is used to kick out above and destroy yarns and other, but
      // we dont want to show the user
                                                              
      // To ignore EIdConnClosedGracefully, for instance...
      if not (E is EAbort) then begin
        Server.DoListenException(Self, E);
      end;
    end;
  end;
end;

.......

    // Create and init context
    LContext := Server.FContextClass.Create(LPeer, LYarn, Server.Contexts);
    LContext.FServer := Server;
    // We set these instead of having the context call them directly
    // because they are protected methods. Also its good to keep
    // Context indepent of the server as well.
    LContext.OnBeforeRun := Server.ContextConnected;
    LContext.OnRun := Server.DoExecute;
    LContext.OnAfterRun := Server.ContextDisconnected;
    LContext.OnException := Server.DoException;
    //
    Server.ContextCreated(LContext);
    //
    // If all ok, lets start the yarn
    Server.Scheduler.StartYarn(LYarn, LContext);

通过上下文的事件关联到了Server的执行方法。主要是LContext.OnRun := Server.DoExecute;

TIdCustomTCPServer.DoExecute 没做什么事,就检查了tcpConnected,具体工作在子类。

TIdCustomHTTPServer.DoExecute,这个方法是个复杂的处理(代码太长了,不贴了),主要是循环处理各种HTTP 方法,解析请求头,判断请求类别,归类参数等等,然后调用DoCommandGet(子类通过此方法来具体处理),最后给客户端返回响应Response,直到连接断开。

我们的子类是TIdHTTPWebBrokerBridge,看看其DoCommandGet():


procedure TIdHTTPWebBrokerBridge.DoCommandGet(AThread: TIdContext;
 ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
  if FWebModuleClass <> nil then begin
    // FWebModuleClass, RegisterWebModuleClass supported for backward compatability
    RunWebModuleClass(AThread, ARequestInfo, AResponseInfo)
  end else
  begin
    {$IFDEF HAS_CLASSVARS}
    TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler.Run(AThread, ARequestInfo, AResponseInfo);
    {$ELSE}
    IndyWebRequestHandler.Run(AThread, ARequestInfo, AResponseInfo);
    {$ENDIF}
  end;
end;

开始时,FWebModuleClass = nil;

所以执行的是:

TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler.Run(AThread, ARequestInfo, AResponseInfo);


type
  TIdHTTPWebBrokerBridgeRequestHandler = class(TWebRequestHandler)
  {$IFDEF HAS_CLASSVARS}
  private
   class var FWebRequestHandler: TIdHTTPWebBrokerBridgeRequestHandler;
  {$ENDIF}
  public
    constructor Create(AOwner: TComponent); override;
    {$IFDEF HAS_CLASSVARS}
      {$IFDEF HAS_CLASSDESTRUCTOR}
    class destructor Destroy;
      {$ENDIF}
    {$ENDIF}
    destructor Destroy; override;
    procedure Run(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  end;

这个辅助类是从TWebRequestHandler继承的,其类变量FWebRequestHandler的赋值是通过独立局部函数:
function IdHTTPWebBrokerBridgeRequestHandler: TWebRequestHandler;
begin
  {$IFDEF HAS_CLASSVARS}
  if not Assigned(TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler) then
    TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler := TIdHTTPWebBrokerBridgeRequestHandler.Create(nil);
  Result := TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler;
  {$ELSE}
  if not Assigned(IndyWebRequestHandler) then
    IndyWebRequestHandler := TIdHTTPWebBrokerBridgeRequestHandler.Create(nil);
  Result := IndyWebRequestHandler;
  {$ENDIF}
end;

一般的运用都有编译开关HAS_CLASSVARS,所以执行的是:

  if not Assigned(TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler) then
    TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler := TIdHTTPWebBrokerBridgeRequestHandler.Create(nil);
  Result := TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler;

此函数是在单元的初始化调用,并赋值给了WebReq单元的全局变量WebRequestHandlerProc。

initialization
  WebReq.WebRequestHandlerProc := IdHTTPWebBrokerBridgeRequestHandler;

所以TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler.Run()就相当于

TIdHTTPWebBrokerBridgeRequestHandler.Run()

这里的类变量FWebRequestHandler是为了实现单例模式。

通过如此处理,就把TIdHTTPWebBrokerBridge.DoCommandGet()转到了TIdHTTPWebBrokerBridgeRequestHandler.Run()来处理,这里用了桥接模式。

也就是从TIdCustomHTTPServer转接到了TWebRequestHandler。

注:

TIdHTTPWebBrokerBridge->TIdCustomHTTPServer

TIdHTTPWebBrokerBridgeRequestHandler->TWebRequestHandler

具体看看TIdHTTPWebBrokerBridgeRequestHandler.Run():


procedure TIdHTTPWebBrokerBridgeRequestHandler.Run(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
  LRequest: TIdHTTPAppRequest;
  LResponse: TIdHTTPAppResponse;
begin
  try
    LRequest := TIdHTTPAppRequest.Create(AThread, ARequestInfo, AResponseInfo);
    try
      LResponse := TIdHTTPAppResponse.Create(LRequest, AThread, ARequestInfo, AResponseInfo);
      try
        // WebBroker will free it and we cannot change this behaviour
        AResponseInfo.FreeContentStream := False;
        HandleRequest(LRequest, LResponse);
      finally
        FreeAndNil(LResponse);
      end;
    finally
      FreeAndNil(LRequest);
    end;
  except
    // Let Indy handle this exception
    raise;
  end;
end;

这里通过监听线程获得的XXXInfo参数构造了Request和Response,并调用了父类的HandleRequest(Web.WebReq.pas):


function TWebRequestHandler.HandleRequest(Request: TWebRequest;
  Response: TWebResponse): Boolean;
var
  I: Integer;
  LWebModule: TComponent;
  LWebAppServices: IWebAppServices;
  LGetWebAppServices: IGetWebAppServices;
  LComponent: TComponent;
begin
  Result := False;
  LWebModule := ActivateWebModules;
  if Assigned(LWebModule) then
  try
    try
      if Supports(IInterface(LWebModule), IGetWebAppServices, LGetWebAppServices) then
        LWebAppServices := LGetWebAppServices.GetWebAppServices;
      if LWebAppServices = nil then
        for I := 0 to LWebModule.ComponentCount - 1 do
        begin
          LComponent := LWebModule.Components[I];
          if Supports(LComponent, IWebAppServices, LWebAppServices) then
            if LWebAppServices.Active then
              break
            else
              LWebAppServices := nil;
        end;
      if LWebAppServices = nil then
        LWebAppServices := TDefaultWebAppServices.Create;
      LWebAppServices.InitContext(LWebModule, Request, Response);
      try
        try
          Result := LWebAppServices.HandleRequest;
        except
          ApplicationHandleException(LWebAppServices.ExceptionHandler);
        end;
      finally
        LWebAppServices.FinishContext;
      end;
      if Result and not Response.Sent then
        Response.SendResponse;
    except
      ApplicationHandleException(LWebAppServices.ExceptionHandler);
    end;
  finally
    DeactivateWebModules(LWebModule);
  end;
end;

第一步:

LWebModule := ActivateWebModules;

这个是获取当前活动的Modules。

题外话:Delphi的WebBroker技术经过了多年的发展,随着大环境的发展和变化,其实现方式也不断扩展和进步。系统维护一个Module Pools,单有一个新的客户端请求Request时,就分配一个Modules。

文档:

TWebRequestHandler maintains the Web modules in an application and creates request and response objects when the application receives HTTP request messages.

TWebRequestHandler keeps a pool of active Web modules. In response to a request from the application, TWebRequestHandler creates a request object and assigns it to one of the active Web modules

ActivateWebModules()代码:


function TWebRequestHandler.ActivateWebModules: TComponent;
begin
  if (FMaxConnections > 0) and (FAddingActiveModules >= FMaxConnections) then
    raise EWebBrokerException.CreateRes(@sTooManyActiveConnections);

  FCriticalSection.Enter;
  try
    FAddingActiveModules := FActiveWebModules.Count + 1;
    try
      if (FMaxConnections > 0) and (FActiveWebModules.Count >= FMaxConnections) then
        raise EWebBrokerException.CreateRes(@sTooManyActiveConnections);
      if FInactiveWebModules.Count > 0 then
      begin
        Result := FInactiveWebModules[0];
        FInactiveWebModules.Extract(Result);
        FActiveWebModules.Add(Result);
      end
      else
      begin
        if WebModuleClass <> nil then
        begin
          Result := WebModuleClass.Create(nil);
          FActiveWebModules.Add(Result);
        end
        else
          raise EWebBrokerException.CreateRes(@sNoDataModulesRegistered);
      end;
    finally
      FAddingActiveModules := 0;
    end;
  finally
    FCriticalSection.Leave;
  end;
end;

Module的最大数量=MaxConnections。

这里的WebModuleClass,可以是TWebModule类,如果是旧项目升级,也可以是TDataMudule+TWebDispatcher, DEMO里WebModule单元的全局变量:

var
  WebModuleClass: TComponentClass = TWebModule1;

确定了这个WebModuleClass的类型,所以这里是传类别,不是实例。

WebModuleClass,("if WebModuleClass <> nil then"),这个属性的赋值,我们没有明显的看到在哪里对FWebModuleClass进行赋值,实际这个属性是在外部进行赋值的,查看Demo的项目代码:

program WebServerTest;
{$APPTYPE GUI}

uses
  Vcl.Forms,
  Web.WebReq,
  IdHTTPWebBrokerBridge,
  FormUnit1 in 'FormUnit1.pas' {Form1},
  WebModuleUnit1 in 'WebModuleUnit1.pas' {WebModule1: TWebModule};

{$R *.res}

begin
  if WebRequestHandler <> nil then
    WebRequestHandler.WebModuleClass := WebModuleClass;
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

  if WebRequestHandler <> nil then
    WebRequestHandler.WebModuleClass := WebModuleClass;

这里的WebRequestHandler是个“变量型函数”,定义在Web.WebReq.pas:

  function WebRequestHandler: TWebRequestHandler;

..............
function WebRequestHandler: TWebRequestHandler;
begin
  if Assigned(WebRequestHandlerProc) then
    Result := WebRequestHandlerProc
  else
    Result := nil;
end;

梳理下:

首先IdHTTPWebBrokerBridge.pas的初始化部分:

initialization
  WebReq.WebRequestHandlerProc := IdHTTPWebBrokerBridgeRequestHandler; 

WebReq.pas声明函数指针WebRequestHandlerProc:

var
  WebRequestHandlerProc: function: TWebRequestHandler = nil;

使得WebReq单元的WebRequestHandlerProc函数指针指向IdHTTPWebBrokerBridgeRequestHandler(),

执行  if WebRequestHandler <> nil then,调用了function WebRequestHandler: TWebRequestHandler,然后调用IdHTTPWebBrokerBridgeRequestHandler(),获得了TWebRequestHandler的实例,然后设置此实例的WebModuleClasss属性值:

WebRequestHandler.WebModuleClass := WebModuleClass;

此实例间接保存在全局函数指针WebRequestHandlerProc。

第二步:

判断WebModuleClass本身是否支持IGetWebAppServices,如果不支持,就检查WebModuleClass里的组件是否支持IWebAppServices,比如前面说的TDataMudule+TWebDispatcher,就通过TWebDispatcher获得。

没有组件支持IWebAppServices,就进行下一步,直接创建了默认的WebServices:

LWebAppServices := TDefaultWebAppServices.Create;

注: 我们可以试试,如果拖一个TWebDispatcher到TWebModule界面,就提示错误信息:“TWebDispatcher”组件只能添加到TDataModule或TForm,服务器只能有一个WebDispatcher,文档里也强调了,服务器只能有一个TWebModule类,这个是强制要求。

获取LWebAppServices后,初始化LWebAppServices的上下文:

LWebAppServices.InitContext(LWebModule, Request, Response);

然后调用LWebAppServices.HandleRequest,这样就到了真正处理Service的地方了。

这里我们看看TDefaultWebAppServices.HandleRequest(Web.HTTPApp.pas):


function TDefaultWebAppServices.HandleRequest: Boolean;
begin
  Result := InvokeDispatcher;
end;


function TDefaultWebAppServices.InvokeDispatcher: Boolean;
begin
  if RequestHandler <> nil then
  begin
    Result := RequestHandler.HandleRequest(Request, Response);
  end
  else
    raise EWebBrokerException.CreateRes(@sNoDispatcherComponent);
end;


function TDefaultWebAppServices.GetRequestHandler: IWebRequestHandler;
begin
  if FRequestHandler = nil then
    FRequestHandler := FindRequestHandler;
  Result := FRequestHandler;
end;

function TDefaultWebAppServices.FindRequestHandler: IWebRequestHandler;
var
  Component: TComponent;
begin
  Result := nil;
  Component := FindWebDispatcher;
  if Component <> nil then
    if not Supports(Component, IWebRequestHandler, Result) then
      Assert(False, 'Expect support for IWebRequestHandler');  { do not localize }
end;

function TDefaultWebAppServices.FindWebDispatcher: TComponent;
var
  J: Integer;
begin
  Result := nil;
  if WebModule is TCustomWebDispatcher then
    Result := WebModule
  else
    for J := 0 to WebModule.ComponentCount - 1 do
      if WebModule.Components[J] is TCustomWebDispatcher then
      begin
        Result := WebModule.Components[J];
        break;
      end;
end;

在InvokeDispatcher()方法里,接口RequestHandler(类型为IWebRequestHandler)是通过GetRequestHandler()获得,注意GetRequestHandler()的实现,FRequestHandler是个单例变量。

通过FindWebDispatcher()方法,最终找到实现了IWebRequestHandler接口的组件,也就是TWebModule或者TDataModule里的组件TWebDispatcher。

TWebModule和TWebDispatcher都是TCustomWebDispatcher的子类。

通过RequestHandler.HandleRequest(Request, Response)转到TCustomWebDispatcher.HandleRequest():


function TCustomWebDispatcher.HandleRequest(
  Request: TWebRequest; Response: TWebResponse): Boolean;
begin
  FRequest := Request;
  FResponse := Response;
  Result := DispatchAction(Request, Response);
end;

到这里,我们看到了"Action"字眼,爬山涉水的,露出了曙光。。。

瞧瞧DispatchAction():


function TCustomWebDispatcher.DispatchAction(Request: TWebRequest;
  Response: TWebResponse): Boolean;
var
  I: Integer;
  Action, Default: TWebActionItem;
  Dispatch: IWebDispatch;
begin
  FRequest := Request;
  FResponse := Response;
  I := 0;
  Default := nil;
  if Response.Sent then
  begin
    Result := True;
    { Note that WebSnapSvr enabled apps have no way to mark response as sent }
    Exit;
  end;
  Result := DoBeforeDispatch(Request, Response) or Response.Sent;
  while not Result and (I < FActions.Count) do
  begin
    Action := FActions[I];
    Result := Action.DispatchAction(Request, Response, False);
    if Action.Default then Default := Action;
    Inc(I);
  end;
  // Dispatch to self registering components
  I := 0;
  while not Result and (I < FDispatchList.Count) do
  begin
    if Supports(IInterface(FDispatchList.Items[I]), IWebDispatch, Dispatch) then
    begin
      Result := DispatchHandler(Self, Dispatch,
        Request, Response, False);
    end;
    Inc(I);
  end;

  if not Result and Assigned(Default) then
    Result := Default.DispatchAction(Request, Response, True);
  if Result and not Response.Sent then
    Result := DoAfterDispatch(Request, Response);

end;

用户可以在TWebModule里添加动作,就是熟悉的Route->Action的那个"Action"。

首先判断本次会话是否已经完成,如代码:

  if Response.Sent then
  begin
    Result := True;
    { Note that WebSnapSvr enabled apps have no way to mark response as sent }
    Exit;
  end;

是否已经发送完Response;


  Result := DoBeforeDispatch(Request, Response) or Response.Sent;
注意这一行代码!!!这里可以让我们有机会插入请求处理过程及结果。嗯,我们可以在这里"截胡"。

DoBeforeDispatch()调用OnBeforeDispatch
procedure TWebModule1.WebModuleBeforeDispatch(Sender: TObject; Request: TWebRequest;
  Response: TWebResponse; var Handled: Boolean);
begin
//可以预处理或者完全处理,

//Handled := True会中段后续的处理

//调用Response.Send(),会中段后续的处理
end;

如果前面没有中断处理,则会继续处理后续的"Action",如果有的话。

到这里流程就基本明白了,如何从获取用户的请求,到我们在Server定制的功能。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
信息科学与工程学院课程设计任务书 题 目: 网上书店后台管理系统 姓 名: xxxxxxxxxxxxxxxxxxx 姓 名: xxxxxxxxxxxxxxxxxxx 姓 名: xxxxxxxxxxxxxxxxxxx 专业班级: 计算机科学与技术 课 程: Java Web程序设计 指导教师: 职称: 完成时间: 2016年 5 月----2016年 6月 2016年6 月27日 课程设计任务书及成绩评定 "课程设计的任务和具体要求 " " " "课程设计要求设计一个用Java Web开发基于B/S架构的管理系统,页面至 " "少6个以上,应能反应出学生综合利用Java Web知识和数据库知识完成一 " "定的设计任务的能力,反映出学生理论联系实践的动手能力。 " "具体要求如下: " "(1)明确所要开发系统的设计任务; " "(2)做好需求分析,合理选择设计方案; " "(3)页面静态部分可用Dreamweaver开发实现 " "(4)动态页面部分可用JSP、Servlet、JavaBean等实现; " "(5)后台数据库可采用MySQL、Oracle、SQL Server等实现; " "(6)在编写程序过程中应注意相关文档的编写; " "(7)认真撰写课程设计总结报告。 " " " " " " " " " " " "指导教师签字: _______ 日期: " "指导教师评语 " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "成绩:____________ 指导教师签字: 日期: " " " "课程设计所需软件、硬件等 " " " "硬件环境: CPU,主频1GHz以上;存512M以上; " "硬盘30G以上;1024×768显示分辨率 " "软件环境: Microsoft windows XP或以上版本 ; eclipse; JDK1.7; " "Tomcat7.0; MySQL " " " "课程设计进度计划 " "起至日期 "工作容 "备注 " " "课程设计要求设计一个用Java " " "2016.5.30-2016.6. "Web开发基于B/S架构的管理系统," " "20 "页面至少6个以上,应能反应出学 " " " "生综合利用Java " " " "Web知识和数据库知识完成一定的 " " " "设计任务的能力,反映出学生理论" " " "联系实践的动手能力。 " " "参考文献、资料索引 " "序号 "文献、资料名称 "编著者 "出版单位 " "[1]宇君.SQL server数据库应用设计案例汇编[M].:中国铁道, " "2007.86-132 " "[2]童爱红等.Delphi数据库编程[M].:清华大学, 2005.106-158 " "[3]波.基于Delphi的学生成绩管理系统[J].大学学报,2009,(10):283 " " " " " "目 录 " "1系统介绍………………………………………………………………………4 " "2数据库表的结构描述及其关系……………………………………………4 " "3软件的模块结构图……………………………………………………………4 " "4主要功能模块的流程图………………………………………………………5 " "5 主要功能 " "5.1注册登录 ………………………………………………………………5 " "  5.2 购物车 …………………………………………………………………7 " "5.3 后台登录 ………………………………………………………………9 " "5.4 用户管理 ………………………………………………………………10 " " " "5.5图书管理 ………………………………………………………………12 " " " "6心得体会………………………………………………………………………14 " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "网上书店 " "1系统介绍 " "网上书店系统是电子商务的一类重要应用领域,通过它可以进行在线商品" "交易。随着互联网的普及以及电子商务的发展和人们购物理念与购物方式" "的改变,网上书店系统将有着巨大的市场潜力。本文在研究电子商务,特" "别是网上书店系统的基本理论和关键技术的基础上,对网上书店系统的发" "展背景,各种实现 技术,以及各种实现技术的优缺点和网上书店系统的 " "安全策略进行了分析。 " " " " " " " " " "2数据库表的结构描述及其关系 " "在mysql中创建
信息科学与工程学院课程设计任务书 题 目:网上书店后台管理系统 __##################x __##################x __##################x 专业__计算机科学与技术 课 程: Java Web程序设计 指导 职称: 完成时间: 2016年 5 月----2016年 6月 2016年6 月27日 课程设计任务书与成绩评定 "课程设计的任务和具体要求 " " " "课程设计要求设计一个用Java " "Web开发基于B/S架构的管理系统,页面至少6个以上,应能反应出学生综合 " "利用Java Web知识和数据库知识完成一定的设计任务的能力,反映出学生 " "理论联系实践的动手能力. " "具体要求如下: " "〔1〕明确所要开发系统的设计任务; " "〔2〕做好需求分析,合理选择设计方案; " "〔3〕页面静态部分可用Dreamweaver开发实现 " "〔4〕动态页面部分可用JSP、Servlet、JavaBean等实现; " "〔5〕后台数据库可采用MySQL、Oracle、SQL Server等实现; " "〔6〕在编写程序过程中应注意相关文档的编写; " "〔7〕认真撰写课程设计总结报告. " " " " " " " " " " " "指导教师签字:_______ 日期: " "指导教师评语 " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "成绩:____________指导教师签字: 日期: " "课程设计所需软件、硬件等 " " " "硬件环境: CPU,主频1GHz以上;内存512M以上; " "硬盘30G以上;1024×768显示分辨率 " "软件环境: Microsoft windows XP或以上版本 ; eclipse; JDK1.7; " "Tomcat7.0; MySQL " " " "课程设计进度计划 " "起至日期 "工作内容 "备注 " " "课程设计要求设计一个用Java " " "2016.5.30-2016.6. "Web开发基于B/S架构的管理系统, " " "20 "页面至少6个以上,应能反应出学生" " " "综合利用Java " " " "Web知识和数据库知识完成一定的 " " " "设计任务的能力,反映出学生理论 " " " "联系实践的动手能力. " " "参考文献、资料索引 " "序号 "文献、资料名称 "编著者 "出版单位 " "[1]刘宇君.SQL server数据库应用设计案例汇编[M].:中国铁道, " "2007.86-132 " "[2]童爱红等.Delphi数据库编程[M].:清华大学, 2005.106-158 " "[3]刘波.基于Delphi的学生成绩管理系统[J].四川大学学报,2009,<10>:2" "83 " " " " " "目 录 " "1系统介绍………………………………………………………………………4 " "2数据库表的结构描述与其关系……………………………………………4 " "3软件的模块结构图……………………………………………………………4 " "4主要功能模块的流程图………………………………………………………5 " "5 主要功能 " "5.1注册登录 ………………………………………………………………5 " "5.2 购物车 …………………………………………………………………7 " "5.3 后台登录 ………………………………………………………………9 " "5.4 用户管理………………………………………………………………10 " " " "5.5图书管理………………………………………………………………12 " " " "6心得体会………………………………………………………………………14 " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "网上书店 " "1系统介绍 " "网上书店系统是电子商务的一类重要应用领域,通过它可以进行在线商品 " "交易.随着互联网的普与以与电子商务的发展和人们购物理念与购物方式 " "的改变,网上书店系统将有着巨大的市场潜力.本文在研究电子商务,特别 " "是网上书店系统的基本理论和关键技术的基础上,对网上书店系统的发展 " "背景,各种实现 技术,以与各种实现技术的优缺点和网上书店系统的安全 " "策略进行了分析. " " " " " " " " " "2数据库表的结构描述与其关系 " "在mysql中创建一个名为"tes
信息科学与工程学院课程设计任务书   题 目:   网上书店后台管理系统      姓  名:   xxxxxxxxxxxxxxxxxxx    姓   名:     xxxxxxxxxxxxxxxxxxx    姓   名:     xxxxxxxxxxxxxxxxxxx 专业班级:     计算机科学与技术   课  程:   Java Web程序设计     指导教师:     职称:             完成时间:  2016年 5 月--—-2016年 6月 2016年6 月27日 课程设计任务书及成绩评定 "课程设计得任务与具体要求 " "课程设计要求设计一个用Java " "Web开发基于B/S架构得管理系统,页面至少6个以上,应能反应出学生综 " "合利用Java Web知识与数据库知识完成一定得设计任务得能力,反映 " "出学生理论联系实践得动手能力。 " "具体要求如下: " "(1)明确所要开发系统得设计任务; " "(2)做好需求分析,合理选择设计方案; " "(3)页面静态部分可用Dreamweaver开发实现 " "(4)动态页面部分可用JSP、Servlet、JavaBean等实现; " "(5)后台数据库可采用MySQL、Oracle、SQL Server等实现; " "(6)在编写程序过程中应注意相关文档得编写; " "(7)认真撰写课程设计总结报告。 " "指导教师签字:     _______   日期:      " "指导教师评语 " "成绩:____________  指导教师签字:      日期: " " " "课程设计所需软件、硬件等 " "硬件环境: CPU,主频1GHz以上;内存512M以上; " "   硬盘30G以上;1024×768显示分辨率 " "软件环境: Microsoft " "windows XP或以上版本 ; eclipse; JDK1、7; Tomcat7、0; " "MySQL " "课程设计进度计划 " "起至日期 "工作内容 "备注 " "2016、5、30-2016、"课程设计要求设计一个用Java  " " "6、 "Web开发基于B/S架构得管理系统" " "20 ",页面至少6个以上,应能反应出 " " " "学生综合利用Java " " " "Web知识与数据库知识完成一定得 " " " "设计任务得能力,反映出学生理论" " " "联系实践得动手能力. " " "参考文献、资料索引 " "序号 "文献、资料名称 "编著者 "出版单位 " "[1]刘宇君、SQL server数据库应用设计案例汇编[M]、北京:中国 " "铁道出版社, 2007、86—132 " "[2]童爱红等、Delphi数据库编程[M]、北京:清华大学出版社, " "2005、106-158 " "[3]刘波、基于Delphi得学生成绩管理系统[J]、四川大学学报,2009" ",(10):283 " "目 录 " "1系统介绍………………………………………………………………………4 " "2数据库表得结构描述及其关系……………………………………………4 " "3软件得模块结构图……………………………………………………………4 " "4主要功能模块得流程图………………………………………………………5 " "5 主要功能 " "5、1注册登录 ………………………………………………………………5 " " 5、2 购物车 …………………………………………………………………7 " "5、3 后台登录 ………………………………………………………………9 " " 5、4 用户管理 ………………………………………………………………10 " " 5、5图书管理 ………………………………………………………………12 " "6心得体会………………………………………………………………………14 " "网上书店 " "1系统介绍 " "网上书店系统就是电子商务得一类重要应用领域,通过它可以进行在线商 " "品交易.随着互联网得普及以及电子商务得发展与人们购物理念与购物方 " "式得改变,网上书店系统将有着巨大得市场潜力.本文在研究电子商务,特" "别就是网上书店系统得基本理论与关键技术得基础上,对网上书店系统得 " "发展背景,各种实现 技术,以及各种实现技术得优缺点与网上书店系统 " "得安全策略进行了分析. " "2数据库表得结构描述及其关系 " "在mysql中创建一个名为"test"得数据库。在数据库中建立一个名为"us" "erdetail"得表。 " "表中有五个字段:username(用户名)、userpass(密码)、role(" "权限)、retime(注册时间)与l

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值