4.2. LOG FILTER
Delphi 2010 DataSnap 允许自定义传输过滤器.我们可以从TTransportFilter 类型继承自己的类.在这个新
类中,可以重写基类中的方法,实现这些方法.例如我们创建一个TLogFilter类:
unit LogFilter;
interface
uses
SysUtils, DBXPlatform, DBXTransport;
type
TLogFilter = class(TTransportFilter) ;
private
protected
function GetParameters: TDBXStringArray; override;
function GetUserParameters: TDBXStringArray; override;
public
function GetParameterValue(const ParamName: UnicodeString): UnicodeString; override;
function SetParameterValue(const ParamName: UnicodeString;
const ParamValue: UnicodeString): Boolean; override;
constructor Create; override;
destructor Destroy; override;
function ProcessInput(const Data: TBytes): TBytes; override;
function ProcessOutput(const Data: TBytes): TBytes; override;
function Id: UnicodeString; override;
end;
const
LogFilterName = 'Log'
这个类的很多方法实现都是空的:由于仅仅用于记录ProcessInput和ProcessOutput方法传输的数据,很多方
法都不用实现.非空方法如下:
function TLogFilter.SetParameterValue(const ParamName, ParamValue: UnicodeString): Boolean;
begin
Result := True;
end;
constructor TLogFilter.Create;
begin
inherited Create;
end;
destructor TLogFilter.Destroy;
begin
inherited Destroy;
end;
function TLogFilter.ProcessInput(const Data: TBytes): TBytes;
begin
Result := Data; // log incoming data
end;
function TLogFilter.ProcessOutput(const Data: TBytes): TBytes;
begin
Result := Data; // log outgoing data
end;
function TLogFilter.Id: UnicodeString;
begin
Result := LogFilterName;
end;
最后,重要的实现部分是在initialization和finalization中注册DataSnap传输过滤器.确保客户端可以找到这个传
输过滤器,并在请求时自动使用.
initialization
TTransportFilterFactory.RegisterFilter(LogFilterName, TLogFilter);
finalization
TTransportFilterFactory.UnregisterFilter(LogFilterName);
end.
为了在DataSnap服务端使用这个传输过滤器,我们需要将其加入到TDSTCPServer或DSHTTPService组件
的Filters属性中,非常简单.在设计时,已知存在一个ZLibCompression过滤器,但无法感知新的过滤器(除非将其
添加到设计时包中并安装).幸运的是我们也可以在运行时添加过滤器,在ServerContainerUnitDemo单元中引用
过滤器单元,然后手动向Filters属性中添加过滤器.如.
procedure TServerContainer1.DataModuleCreate(Sender: TObject);
begin
DSTCPServerTransport1.Filters.AddFilter(LogFilterName);
DSHTTPService1.Filters.AddFilter(LogFilterName);
DSHTTPService1.Active := True;
end;
这将确保服务端使用LogFilter,客户端只要在单元中引用LogFilter单元就会自动使用LogFilter.否则将抛出
错误信息:
注意DataSnap客户端和服务端将获取各自的logfile实例.虽然使用的同一个过滤器,但是却不需用
ParamStr(0)来区分日志信息.
4.3. 加密过滤器
即使是一个如4.2中的一个简单的过滤器,也需要自己去扩展,很复杂.DataSnap提供的过滤器不太完整.事实
上,有大量的三方过滤器可以使用,有Daniele Teti开发的DataSnap Filters Compendium可以
在http://www.danieleteti.it/?p=168中获取,其中不少于9个用于DataSnap2010的附加过滤器,分成三个
组.Hash组支持MD5,MD4,SHA1和SHA512,Cipher组支持Blowfish,Rijndael, 3TDES 和 3DES, Compress
组支持LZO.并且是全源码版.
5. 如何构建DATASNAP WEB项目
除了生成Windows项目,还提供了生成ISAPI,CGI或Web App Debugger目标项目的向导.首先我们讨论
一下每种项目类型的优缺点,并展示如何在一个项目组中同时创建出这三个项目,并让他们共享公共的单元
文件.这样我们就可以为同一个DataSnap项目产生三个不同的部署目标.
虽然到目前为止我们构建的DataSnap服务应用程序运行良好.但是在有些情况下就无法部署服务程序.例
如如果你不能或不允许在防火墙中打开请求的端口让客户端连接服务器.幸运的是,这种情况下我们可以使
用Web服务来部署,而80端口号一直都是打开的.如果我们使用IIS来作为Web服务器,我们就可以使用新的
DataSnap WebBroker应用程序向导来创建一个可部署在IIS上的应用.
DataSnap WebBroker应用程序向导提供了三个选项,第一个选项实际上不是真正的WebBroker应用,但
仅仅Web App Debugger可执行文件,这是用于调试目的的. Web App Debugger可执行文件很强大,允许我
们使用Web App Debugger(Delphi IDE Tooles菜单中)作为调试Web App Debugger应用程序的宿主程序.
调试CGI或ISAPI/NSAPI Web应用非常不方便,因此在开发过程中最好选择Web App Debugger模式.
而ISAPI/NSAPI Dynamic Link Library 和 CGI Stand-alone executable 类型项目可用于真正部署的
DataSnap服务项目上.
注意,选择CGI Stand-alone executable不是一个好主意,因为这个可执行文件将在每次请求中加载卸载.
在加上要连接到数据库执行一些任务,你必须要考虑到应用程序的执行效率.使用ISAPI的DLL形式,只需要
加载一次,保存在内存中,后续请求(可能来自其他用户)不需要再次加载.ISAPI DLL的主要缺点是升级困难
(如果你用FTP连接到Web服务器).但可以联系Web服务供应商.
ISAPI DLL的另一个缺点是调试不方便—必须用IIS作为宿主应用,不能总是按计划的那样运行.但可以用
Web App Debugger executable来解决这个问题—同时创建两个项目,他们使用公共的DataSnap方法和代
码.第一个范例就是这种形式,加入一些实用的功能构建一个框架.
5.3. 服务方法,部署,客户端
增加功能时只需要修改被两个项目共享的ServerMethodUnit1.pas单元.默认有一个范例函数,就是上面的
范例,我们包含多个方法(组件说明和源码见2.1.4.).服务端方法实现后,我们就可以将ISAPI DLL部署到IIS.
详细信息可见http://blogs.embarcadero.com/jimtierney/2009/08/20/31502.
本例中如你没有Web服务可用于部署,可以使用我已部署的DataSnap ISAPI.注意我没有发布
TDataSetProvider,也没有实现返回数据的GetEmployees方法,但是ServerTime和EchoString方法都运行
良好,可以用来测试DataSnap客户端了.
在客户端连接ISAPI DataSnap服务端前,可以使用Data Explorer检查一下是否可以连接到ISAPI
DataSnap服务. Data Explorer上有一个新的叫做DATASNAP的目录,展开后,第一个连接叫做
DataSnapConnection,右键修改连接.在这个对话框中我们可以选择协议,主机(如你没有自己的Web服务可
以使用http://www.bobswart.nl/),端口号,以及ISAPI DataSnap服务应用程序在Web服务器上的URL路径,这里是
cgi-bin/DSISAPIServer.dll.点击测试连接.
点击OK关闭窗口,在Data Explorer,展开DATASNAPCONNECTION节点查看表,视图,过程,函数和
同义词.下图中,过程包括DSAdmin,DSMetaData,TServerMethods1.AS_XXX及我们自定义的三个函数
EchoString,ServerTime 和 GetEmployees.
不需要写DataSnap客户端,现在就可以测试这些方法.例如EchoString方法(发送什么返回什么).右键点击
TServerMethods1.GetEmployees方法,选择View Parameters,弹出一个新窗口,输入参数(例如42).在这个新
窗口中右击,选择”执行远程服务方法”.运行结果将显示在ReturnValue中.
这样我们就可以调用远程DataSnap服务方法.为了在客户端连接到远程服务端,我们只需要修改
TSQLConnection组件的属性.原来我们连接都Windows版本的DataSnap服务,现在我们需要修改设置连接到
Web版本.
注意,如果你使用的是我发布的DSISAPIServer.dll,我已经禁用了TDataSetProvider,而且GetEmployees方法
不返回任何数据.但你可以使用ServerTime 和 EchoString方法.
6.如何使用REST和JSON
DataSnap2010支持REST和JSON.DataSnap2010特性REST支持DataSnap HTTP请求.例如,如果
DataSnap服务的URI是http://www.bobswart.nl/cgi-bin/DSISAPIServer.dll.我们可以在此URL后
加 /datasnap/rest,后跟服务类名称,方法名称和参数.语法如下:
http://server/datasnap/rest/%3Cclass%3E/%3Cmethod%3E/%3Cparameters>
对于我的服务器上的TServerMethod1模块中的ServerTime方法,URL如下:
http://www.bobswart.nl/cgi-bin/DSISAPIServer.dll/datasnap/rest/TServerMethods1/ServerTime
在浏览器中输入这个REST支持的URL,如下图:
在浏览器中返回结果是JSON结构:
{"result":["2009-10-16 16:01:33.145"]}
更多信息见Marco Cantù的Delphi2010和REST客户端白页.
6.1. 回调
除了用REST支持调用DataSnap服务方法外,JSON还用于实现回调方法.DataSnap2010支持客户端回调
函数,使其执行在服务方法上下文中.这样就可以实现客户端调用服务端方法时,服务端就可以调用由客户端传
递好参数的回调函数.
例如,我们修改EchoString方法,向其中添加回调支持.修改后的EchoString方法如下:
function EchoString(Value: string; callback: TDBXcallback): string;
TDBXcallback类定义在DBXJSON单元.在我们实现EchoString方法前,先搞清楚如何在客户端定义回调函
数(毕竟,这是一个可以让服务端调用的客户端方法).
在客户端,我们必须定义一个新类,继承在TDBXCallback,重写其Execute方法.
type
TCallbackClient = class(TDBXCallback)
public
function Execute(const Arg: TJSONValue): TJSONValue; override;
end;
在Execute方法中,有一个TJSONValue类型的参数,可以复制(Clone)这个参数然后设置其具体内
容.Execute方法也返回一个TJSONValue类型的值,这里我们只返回同样的值:
function TCallbackClient.Execute(const Arg: TJSONValue): TJSONValue;
var
Data: TJSONValue;
begin
Data := TJSONValue(Arg.Clone);
ShowMessage('Callback: ' + TJSONObject(Data).Get(0).JSonValue.value);
Result := Data
end;
例如, 在方法实际返回前(如方法正在执行),回调函数将显示EchoString方法传递参数的值.服务端新的
EchoString方法实现需要将String值赋给一个TJSONObject对象,并将其传递给回调函数.如下:
function TServerMethods2.EchoString(Value: string; callback: TDBXcallback): string;
var
msg: TJSONObject;
pair: TJSONPair;
begin
Result := Value;
msg := TJSONObject.Create;
pair := TJSONPair.Create('ECHO', Value);
pair.Owned := True;
msg.AddPair(pair);
callback.Execute(msg);
end;
注意这个回调函数将在客户端执行—然后在服务端Echostring方法执行完毕前返回
最后,在客户端调用EchoString方法也需要修改,因为我们现在要提供一个回调类TCallbackClient的实例,如
下所示:
var
MyCallback: TCallbackClient;
begin
MyCallback := TCallbackClient.Create;
try
Server.EchoString(Edit1.text, MyCallback);
finally
MyCallback.Free;
end;
end;
这个范例阐释了如何在DataSnap2010中使用客户端回调函数.
7. 使用DATASNAP 和 .NET
Delphi Prism 2010可用来构建使用我们先前生成Wind32服务的DataSnap .NET客户端.为了构建Delphi
Prism 2010 DataSnap客户端,先确保DataSnap服务正常运行.
启动Delphi Prism 2010,点击View. Server Explorer启动Delphi Prism Server Explorer.首先建立一个连
接,验证我们将以使用的DataSnap服务.
Server Explorer的根节点叫做Data Connections.右击Data Connections选择添加连接.对话框如下,在
Data Sources列表中选择DataSnap(注意如果数据源已经预选好了我们需要点击变更一下)
不要选中 Always use this selectiong.除非你一直构建DataSnap数据连接.
点击Continue按钮进入下一步.指定连接的DataSnap服务详细信息.在协议下拉框中选择TCP/IP或HTTP.
接下来,指定服务器(运行DataSnap服务的主机名称,如在本机测试可指定localhost),然后指定端口号.默认
HTTP为80端口,TCP/IP为211端口.但从本白页中可知这两个端口都应该修改,并确保和你在
ServerContainerUnitDemo单元中设置的端口号对应.下一个属性包含路径,这在你要连接到基于Web
Broker的DataSnap服务上很重要.设置为http://后面的部分.
最后,不要忘记验证用户和密码,本例DataSnap服务使用HTTP验证.
点击测试按钮,验证连接.如果弹出连接成功信息表示连接可用.
点击OK按钮,在连接树中显示了一个新的DataSnap连接.本例中是localhost节点.展开这个节点,显示表,
视图,存储过程子节点.表和视图节点为空,但存储过程节点包括所有在DataSnap服务端定义的服务方法.包
括我们自定义的EchoString,GetEmployees和ServerTime.
我们现在可以在Server Explorer中测试服务方法.例如,右击EchoString方法,选择查看参数.弹出新窗口,
输入参数.这里输入42.右击窗口选择执行.将执行服务端的EchoString方法.如下图.
更好的是可以使用GetEmployees方法演示如何从Employees表中获取数据.这个存储过程没有参数,但还
有选择 View Parambers命令,返回一个空参数列表.右击窗体选择 执行.这是返回一个记录集.如下图:
7.1. WINFORMS 客户端
虽然已经可以运行服务端方法了,但更有用的方法是在.NET应用程序中调用这些方法.最后一个范
例,File.New Project启动Delphi Prism新项目向导.选择项目类型.
在Windows 项目类型中选择Windows Application,修改WindowsApplication1为DataSnapClient.
点击OK按钮,创建一个带有Main.pas单元的新项目.
在Server Explorer,选择新建的DataSnap服务连接,属性框中找到ConnectionString,如下:
communicationprotocol=http;hostname=localhost;port=8080;dsauthenticationuser=Bob;ds
authenticationpassword=Swart
右击数据连接节点,选择生成客户端代理(Generate Client Proxy)选项.生成新文件ClientProxy1.pas,其中
定义了TServerMethods1Client类及其中的方法(EchoString, ServerTime, 和GetEmployees)..如下:
TServerMethods1Client = class
public
constructor (ADBXConnection: TAdoDbxConnection);
constructor (ADBXConnection: TAdoDbxConnection; AInstanceOwner: Boolean);
function EchoString(Value: string): string;
function ServerTime: DateTime;
function GetEmployees: System.Data.IDataReader;
除了代理类还在项目的引用节点中添加了Borland.Data.AdoDbxClient 和Borland.Data.DbxClientDriver
引用.
从TServerMethods1Client类代码片段中可见,类有两个构造函数:有使用了一个ADBXConnection参数,
第二个构造函数还有一个AInstanceOwner的Boolean类型参数.这意味着我们必须使用参数调用构造函数.
为了支持这个功能,必须修改项目属性设置.在解决方案管理器中右击DataSnapClient,选择属性.如下图,点
击Compatibility标签,选中”Allow Create constructor calls”,将允许我们调用.Create构造方法,传递参数,而不
仅仅是使用new关键字.
现在回到主窗体,添加一个按钮.在Click事件中创建一个DataSnap服务连接并调用方法.
method MainForm.button1_Click(sender: System.Object; e: System.EventArgs);
var
Client: ClientProxy1.TServerMethods1Client;
Connection: Borland.Data.TAdoDbxDatasnapConnection;
begin
Connection := new Borland.Data.TAdoDbxDatasnapConnection();
Connection.ConnectionString :=
'communicationprotocol=http;hostname=localhost;port=8080;dsauthenticationuser=Bob;dsauth
enticationpassword=Swart';
Connection.Open;
try
Client := ClientProxy1.TServerMethods1Client.Create(Connection);
MessageBox.Show(
Client.EchoString('Delphi Prism 2010'));
finally
Connection.Close;
end;
end;
运行结果如下图所示:
同样方式,我们调用GetEmployees方法获取结果集并显示到DataGridView.这里有个小问题,
DataGridView方法返回的是IDataReader(等价于TSQLDataSet结果集),而不是DataSet和DataTable.我们
必须写几行代码将GetEmployees返回的结果集保存到DataSet的DataTabl中(等价于Win32中的
TClientDataSet).
method MainForm.button1_Click(sender: System.Object; e: System.EventArgs);
var
Client: ClientProxy1.TServerMethods1Client;
Connection: Borland.Data.TAdoDbxDatasnapConnection;
Employees: System.Data.IDataReader;
ds: System.Data.DataSet;
dt: System.Data.DataTable;
begin
Connection := new Borland.Data.TAdoDbxDatasnapConnection();
Connection.ConnectionString :=
'communicationprotocol=http;hostname=localhost;port=8080;dsauthenticationuser=Bob;dsauthenticationpassword=Swart';
Connection.Open;
try
Client := ClientProxy1.TServerMethods1Client.Create(Connection);
Employees := Client.GetEmployees;
ds := new DataSet();
dt := new DataTable("DataSnap");
ds.Tables.Add(dt);
ds.Load(Employees, LoadOPtion.PreserveChanges, ds.Tables[0]);
dataGridView1.DataSource := ds.Tables[0];
MessageBox.Show(
Client.EchoString('Delphi Prism 2010'));
finally
Connection.Close;
end;
end;