> 文章列表 > Delphi DataSnap 流程分析(二)

Delphi DataSnap 流程分析(二)

Delphi DataSnap 流程分析(二)

Delphi DataSnap 流程分析(一)_看那山瞧那水的博客-CSDN博客

粗略分析了 创建传统DataSnap的流程,现在再分析下创建现在更常用的 方式:

DataSnap REST Application

这种方式只支持HTTP(普通HTTP和REST HTTP)通信,不支持TCP通信。

 这种方式包含有WebModule,HTTP服务器也是TIdHTTPWebBrokerBridge

 因此其HTTP Server的启动流程和 Delphi Web Server 流程分析_看那山瞧那水的博客-CSDN博客

里分析的一样,只是到了最后,也就是方法TCustomWebDispatcher.DispatchAction(),接着进行后续处理。

代码: 


constructor TCustomWebDispatcher.Create(AOwner: TComponent);
varI: Integer;Component: TComponent;SetAppDispatcher: ISetAppDispatcher;
begin
{$IFDEF MSWINDOWS}
{$ENDIF}FDispatchList := TObjectList<TComponent>.Create;//TComponentList.Create;FDispatchList.OwnsObjects := False;FOnException := nil;if AOwner <> nil thenif AOwner is TCustomWebDispatcher thenraise EWebBrokerException.Create(sOnlyOneDispatcher)elsefor I := 0 to AOwner.ComponentCount - 1 doif AOwner.Components[I] is TCustomWebDispatcher thenraise EWebBrokerException.Create(sOnlyOneDispatcher);inherited CreateNew(AOwner, -1);FActions := TWebActionItems.Create(Self, TWebActionItem);if Owner <> nil thenfor I := 0 to Owner.ComponentCount - 1 dobeginComponent := Owner.Components[I];if Supports(IInterface(Component), ISetAppDispatcher, SetAppDispatcher) thenSetAppDispatcher.SetAppDispatcher(Self)else if Supports(IInterface(Component), IWebDispatch) thenFDispatchList.Add(Component);end;
end;

TCustomWebDispatcher类创建的时候,会加载支持接口IWebDispatch的组件,TDSHTTPWebDispatcher是支持IWebDispatch接口的:

TDSHTTPWebDispatcher = class(TDSHTTPServerTransport, IWebDispatch) 

所以FDispatchList列表包含了WebModule的组件DSHTTPWebDispatcher1。

 
function TCustomWebDispatcher.DispatchAction(Request: TWebRequest;Response: TWebResponse): Boolean;
varI: Integer;Action, Default: TWebActionItem;Dispatch: IWebDispatch;
beginFRequest := Request;FResponse := Response;I := 0;Default := nil;if Response.Sent thenbeginResult := 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) dobeginAction := FActions[I];Result := Action.DispatchAction(Request, Response, False);if Action.Default then Default := Action;Inc(I);end;// Dispatch to self registering componentsI := 0;while not Result and (I < FDispatchList.Count) dobeginif Supports(IInterface(FDispatchList.Items[I]), IWebDispatch, Dispatch) thenbeginResult := DispatchHandler(Self, Dispatch,Request, Response, False);end;Inc(I);end;if not Result and Assigned(Default) thenResult := Default.DispatchAction(Request, Response, True);if Result and not Response.Sent thenResult := DoAfterDispatch(Request, Response);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;

因而执行Dispatch := DSHTTPWebDispatcher1(TDSHTTPWebDispatcher类),

DispatchHandler(): 这是一个局部方法


function DispatchHandler(Sender: TObject; Dispatch: IWebDispatch; Request: TWebRequest; Response: TWebResponse;DoDefault: Boolean): Boolean;
beginResult := False;if (Dispatch.Enabled and ((Dispatch.MethodType = mtAny) or(Request.MethodType = Dispatch.MethodType)) andDispatch.Mask.Matches(string(Request.InternalPathInfo))) thenbeginResult := Dispatch.DispatchRequest(Sender, Request, Response);end;
end;

进行一些判断,比如格式 /data、/rest等路径格式,调用:


function TDSHTTPWebDispatcher.DispatchRequest(Sender: TObject;Request: TWebRequest; Response: TWebResponse): Boolean;
begintryif Owner is TWebModule thenDataSnapWebModule := TWebModule(Owner);trytryRequiresServer;TDSHTTPServerWebBroker(Self.FHttpServer).DispatchDataSnap(Request, Response);Result := True;excepton E: Exception dobegin{ Default to 500, like web services. }Response.StatusCode := 500;Result := True;end;end;except{ Swallow any unexpected exception, it will bring down some web servers }Result := False;end;finally{ Reset current DataSnapWebModule }DataSnapWebModule := nil;end;
end;

这里的RequiresServer()是调用祖先类的:

 
procedure TCustomDSRESTServerTransport.RequiresServer;
beginif FRestServer = nil thenbeginFRESTServer := CreateRESTServer;InitializeRESTServer;end;
end;function TCustomDSHTTPServerTransport.CreateRESTServer: TDSRESTServer;
beginFHttpServer := CreateHttpServer;Result := FHttpServer;
end;function TDSHTTPService.CreateHttpServer: TDSHTTPServer;
varLHTTPServer: TDSHTTPServerIndy;
beginif Assigned(FCertFiles) thenLHTTPServer := TDSHTTPSServerIndy.Create(Self.Server, IPImplementationID)elseLHTTPServer := TDSHTTPServerIndy.Create(Self.Server, IPImplementationID);Result := LHTTPServer;LHTTPServer.HTTPOtherContext := HTTPOtherContext;
end;

获得RestServer和httpServer。然后调用:


procedure TDSHTTPServerWebBroker.DispatchDataSnap(ARequest: TWebRequest;AResponse: TWebResponse);
varLDispatch: TDSHTTPDispatch;LContext: TDSHTTPContextWebBroker;
beginLDispatch := TDSHTTPApplication.Instance.HTTPDispatch;if LDispatch <> nil thenDoCommand(LDispatch.Context, LDispatch.Request, LDispatch.Response)elsebeginLContext := TDSHTTPContextWebBroker.Create(ARequest, AResponse);tryDoCommand(LContext, LContext.FRequest, LContext.FResponse);finallyLContext.Free;end;end;
end;

又到了DoCommand()方法了

DoCommand():


procedure TDSRESTServer.DoCommand(AContext: TDSHTTPContext; ARequestInfo: TDSHTTPRequest;AResponseInfo: TDSHTTPResponse);
varRequest: string;NextRequest: string;NextContext: string;RestCtxt: string;StartDispatch: Boolean;
begin// HTTPDispatch object if necessaryStartDispatch := not TDSHTTPApplication.Instance.Dispatching;if StartDispatch thenTDSHTTPApplication.Instance.StartDispatch(AContext, ARequestInfo, AResponseInfo);try
{$IFNDEF POSIX}if CoInitFlags = -1 thenCoInitializeEx(nil, COINIT_MULTITHREADED)elseCoInitializeEx(nil, CoInitFlags);
{$ENDIF}try// check for context, if not found send the appropriate error messageRequest := ARequestInfo.URI;if Consume(FDSContext, Request, NextRequest) thenbeginRequest := NextRequest;if Consume(FRESTContext, Request, NextRequest) thenbegin// datasnap/restDoDSRESTCommand(ARequestInfo, AResponseInfo, NextRequest);endelse if ConsumeOtherContext(Request, NextContext, NextRequest) thenbeginDoDSOtherCommand(AContext, ARequestInfo, AResponseInfo, NextContext, NextRequest, FDSServerName <> EmptyStr);endelsebeginRestCtxt := Trim(FRESTContext);if RestCtxt = EmptyStr thenRestCtxt := SProtocolRestEmpty;AResponseInfo.ResponseNo := 501; {rest or other service not found in URI}AResponseInfo.ContentText := Format(SProtocolNotSupported, [Request, RestCtxt]);AResponseInfo.CloseConnection := true;end;endelsebegin// This may dispatch .js files for exampleDoCommandOtherContext(AContext, ARequestInfo, AResponseInfo, Request);end;if Assigned(Self.FTrace ) thenbeginFTrace(Self, AContext, ARequestInfo, AResponseInfo);end;finallyClearInvocationMetadata();
{$IFNDEF POSIX}CoUnInitialize;
{$ENDIF}end;finallyif StartDispatch thenTDSHTTPApplication.Instance.EndDispatch;end;
end;

看到处理路径包含 /datasnap/rest的处理:DoDSRESTCommand():

                            
// Entry point for rest.  Should be able to create session before calling this method
procedure TDSRESTServer.DoDSRESTCommand(ARequestInfo: TDSHTTPRequest;AResponseInfo: TDSHTTPResponse;Request: string);
varCmdType: TDSHTTPCommandType;ResponseOk: Integer;RESTService: TDSRESTService;Len: Integer;ParamName: string;SessionID: string;Session: TDSSession;IsNewSession: Boolean;SessionFailure: Boolean;RespHandler: TDSServiceResponseHandler;OwnService: Boolean;
beginOwnService := True;RespHandler := nil;CmdType := ARequestInfo.CommandType;ResponseOk := 200;RESTService := CreateRESTService(ARequestInfo.AuthUserName, ARequestInfo.AuthPassword);// process query parametersLen := 0;while (Len < ARequestInfo.Params.Count) and (ResponseOk < 300) dobeginParamName := ARequestInfo.Params.Names[Len];//check for session ID parameter in the URLif (Uppercase(ParamName) = 'SESSIONID') or (Uppercase(ParamName) = 'SID') thenbeginSessionID := ARequestInfo.Params.Values[ParamName]endelse if not RESTService.ProcessQueryParameter(ParamName, ARequestInfo.Params.ValueFromIndex[Len]) thenbeginResponseOK := 409;AResponseInfo.ResponseText := Format(CANNOT_PROCESS_PARAM, [ARequestInfo.Params.Names[Len],ARequestInfo.Params.Values[ARequestInfo.Params.Names[Len]]]);end;Inc(Len);end;if (ResponseOK < 300) and not RESTService.CheckConvertersForConsistency thenbegin// 409 - Indicates that the request could not be processed because of conflict in the requestAResponseInfo.ResponseNo := 409;AResponseInfo.ResponseText := QUERY_PARAM_CONFLICT;end;//if no session ID is given in the URL, then try to load it from the Pragma header fieldif SessionID = EmptyStr thenbeginSessionID := TDSHTTPApplication.Instance.GetRequestSessionId(aRequestInfo, False);end;//Try to load the session with the given session ID into the current threadSessionFailure :=not TDSHTTPApplication.FInstance.LoadRESTSession(SessionID, ARequestInfo.AuthUserName, FSessionTimeout, FSessionLifetime,nil (*FTunnelService*), FDSHTTPAuthenticationManager, ARequestInfo,IsNewSession);Session := TDSSessionManager.GetThreadSession;//free any stream which was stored from a previous executionif Session <> nil thenbeginSession.LastResultStream.Free;Session.LastResultStream := nil;if not SessionFailure thenUpdateSessionTunnelHook(Request, Session, ARequestInfo);end;if not SessionFailure and IsClosingSession(Request) thenbegintryCloseRESTSession(Session, AResponseInfo);finallyFreeAndNil(RESTService);TDSSessionManager.ClearThreadSession;end;exit;end;tryif SessionFailure thenbeginAResponseInfo.ResponseNo := 403; //ForbiddenAResponseInfo.ResponseText := SESSION_EXPIRED;AResponseInfo.ContentText := '{"SessionExpired":"' + SSessionExpiredMsg + '"}';endelse if ResponseOK >= 300 thenbegin// pre-parsing failed and the decision is in ResponseOK, response text already setAResponseInfo.ResponseNo := ResponseOK;end//don't need to authenticate if returning to a previously authenticated sessionelse if (FDSHTTPAuthenticationManager <> nil) and IsNewSession and not FDSHTTPAuthenticationManager.Authenticate(DATASNAP_CONTEXT, RESTContext, ARequestInfo.AuthUserName, ARequestInfo.AuthPassword,ARequestInfo, AResponseInfo) thenif ARequestInfo.AuthUserName <> EmptyStr thenAResponseInfo.ResponseNo := 403elsebeginAResponseInfo.SetHeaderAuthentication('Basic', 'REST');AResponseInfo.ResponseNo := 401endelsebeginif Session <> nil thenbeginAResponseInfo.Pragma := 'dssession=' + Session.SessionName;AResponseInfo.Pragma := AResponseInfo.Pragma + ',dssessionexpires=' + IntToStr(Session.ExpiresIn);end;OwnService := False;//create the response handler for populating the response infoRespHandler := TDSResponseHandlerFactory.CreateResponseHandler(RESTService, ARequestInfo, TDSHTTPCommandType.hcUnknown, Self);if RespHandler = nil thenbeginAResponseInfo.ResponseNo := 406; //Not Acceptableendelsebeginif RespHandler is  TDSServiceResponseHandler thenbeginTDSServiceResponseHandler(RespHandler).OnParseRequest := Self.OnParseRequest;TDSServiceResponseHandler(RespHandler).OnParsingRequest := Self.OnParsingRequest;end;//add the query parameters to invocation metadataif ARequestInfo.Params.Count > 0 thenGetInvocationMetadata().QueryParams.AddStrings(ARequestInfo.Params);// dispatch to the appropriate servicecase CmdType ofTDSHTTPCommandType.hcGET:RESTService.ProcessGETRequest(Request, nil, nil, OnNameMap, RespHandler);TDSHTTPCommandType.hcPOST:RESTService.ProcessPOSTRequest(Request, ARequestInfo.Params,ByteContent(ARequestInfo.PostStream), OnNameMap, RespHandler);TDSHTTPCommandType.hcPUT:RESTService.ProcessPUTRequest(Request, ARequestInfo.Params,ByteContent(ARequestInfo.PostStream), OnNameMap, RespHandler);TDSHTTPCommandType.hcDELETE:RESTService.ProcessDELETERequest(Request, nil, nil, OnNameMap, RespHandler);elsebeginGetInvocationMetadata().ResponseCode := 501;GetInvocationMetadata().ResponseContent := Format(SCommandNotSupported, [ARequestInfo.Command]);end;end;//populate the Response Info from the execution resultRespHandler.PopulateResponse(AResponseInfo, GetInvocationMetadata());end;end;finallyif RespHandler = nil thenFreeAndNil(RESTService);if RespHandler <> nil thenRespHandler.Close;if OwnService thenFreeAndNil(RESTService);if (GetInvocationMetadata(False) <> nil) andGetInvocationMetadata.CloseSession and(TDSSessionManager.GetThreadSession <> nil) thenbeginif TDSSessionManager.GetThreadSession.SessionName <> '' thenTDSSessionManager.Instance.CloseSession(TDSSessionManager.GetThreadSession.SessionName);TDSSessionManager.ClearThreadSession;end;// Session cleared by TDSHTTPApplication.EndDispatch// TDSSessionManager.ClearThreadSession;end;
end;

这个方法比较啰嗦,要处理各种情况和格式

自动判别命令类型并分别处理:

        // dispatch to the appropriate service
        case CmdType of
          TDSHTTPCommandType.hcGET:
            RESTService.ProcessGETRequest(Request, nil, nil, OnNameMap, RespHandler);
          TDSHTTPCommandType.hcPOST:
            RESTService.ProcessPOSTRequest(Request, ARequestInfo.Params,
              ByteContent(ARequestInfo.PostStream), OnNameMap, RespHandler);
          TDSHTTPCommandType.hcPUT:
            RESTService.ProcessPUTRequest(Request, ARequestInfo.Params,
              ByteContent(ARequestInfo.PostStream), OnNameMap, RespHandler);
          TDSHTTPCommandType.hcDELETE:
            RESTService.ProcessDELETERequest(Request, nil, nil, OnNameMap, RespHandler);
          else
          begin
            GetInvocationMetadata().ResponseCode := 501;
            GetInvocationMetadata().ResponseContent := Format(SCommandNotSupported, [ARequestInfo.Command]);
          end;
        end;

比如Get:


procedure TDSRESTService.ProcessGETRequest(const Request: string; Params: TStrings; Content: TArray<Byte>;const NameMapEvent: TDSRESTMethodNameMapEvent; ResponseHandler: TRequestCommandHandler);
beginProcessREST('GET', Request, nil, NameMapEvent, ResponseHandler);
end;procedure TDSRESTService.ProcessREST(const RequestType: string;const RestRequest: string;const Content: TArray<Byte>;const NameMapEvent: TDSRESTMethodNameMapEvent;const ResponseHandler: TRequestCommandHandler);varParams, Segments: TStrings;ClassName, MethodName, DSMethodName: string;LHandled: Boolean;I: Integer;
beginSegments := TStringList.Create;Params := TStringList.Create;trytry// get class, method name, parametersLHandled := False;ParseRequestSegments(RestRequest, Segments);if ResponseHandler is TDSServiceResponseHandler thenTDSServiceResponseHandler(ResponseHandler).DoParsingRequest(Self,RequestType, Segments, DSMethodName, Params, LHandled);if not LHandled thenbeginif Segments.Count < 2 thenraise TDSServiceException.Create(SInvalidRequestFormat);ClassName := Segments[0];MethodName := Segments[1];if (ClassName = '') or (MethodName = '') thenraise TDSServiceException.Create(SInvalidRequestFormat);for I := 2 to Segments.Count - 1 doParams.Add(Segments[I]);SetMethodNameWithPrefix(RequestType, ClassName, MethodName, NameMapEvent, DSMethodName);end;if ResponseHandler is TDSServiceResponseHandler thenTDSServiceResponseHandler(ResponseHandler).DoParseRequest(Self,RequestType, Segments, DSMethodName, Params);ProcessRequest(DSMethodName, ResponseHandler,procedure(var AConnection: TDBXConnection; var ACommand: TDBXCommand;const ResponseHandler: TRequestCommandHandler)beginProcessParameters(DSMethodName, Params, Content, ACommand);ACommand.ExecuteUpdate;ResponseHandler.AddCommand(ACommand, AConnection); // OwnsACommand := nil;AConnection := nil;end);excepton ex: Exception doProcessException(ResponseHandler, ex);end;finallyParams.Free;Segments.Free;end;
end;

转到:


procedure TDSService.ProcessRequest(const ACommand: string; const AResponseHandler: TRequestCommandHandler; ACallback: TExecuteCallback);
begintryExecute(ACommand, AResponseHandler, ACallback) //, ResponseHandler);excepton ex: Exception doProcessException(AResponseHandler, ex);end;
end;procedure TDSService.Execute(const ACommand: string; const AResponseHandler: TRequestCommandHandler; ACallback: TExecuteCallback);
varDBXConnection: TDBXConnection;DBXCommand: TDBXCommand;
beginDBXCommand := nil;DBXConnection := GetDBXConnection;tryDBXCommand := DBXConnection.CreateCommand;DBXCommand.CommandType := TDBXCommandTypes.DSServerMethod;DBXCommand.Text := ACommand;DBXCommand.Prepare;ACallback(DBXConnection, DBXCommand, AResponseHandler);finallyif DBXCommand <> nil thenDBXCommand.Close;if DBXConnection <> nil thenDBXConnection.Close;DBXCommand.Free;DBXConnection.Free;end;
end;

客户端的方法调用处理比较繁杂

可以看到DataSnap内部的处理还是依赖 DBX框架。
 

少先队网