打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
WebService With BizSnap 二

電子商務新紀元-WebService With BizSnap(不比李维的那篇差!) 二

(2006-06-20 21:07:37)
分类: Delphi技术

魔法的秘密
如你所見! DELPHI 開發WebService 應用程式真的很快速,且直覺。但魔法是不會憑空出現的,
在神秘的魔法背後,必定藏著許多的故事。 在BizSnap 背後的技術就有如魔法故事一樣,
精妙且有趣, 當然! 知道這些並不能加快你的開發速度或程式品質,不過她卻賦與你另一種
神奇的力量,使你能夠在程式不能正常運作時,迅速找到解決的方法。
首先我們先看看當Service Server 接收到來自Client AP的要求時的處理方式:


當Client AP 第一次呼叫WebService 時,HTTPRIO 會經由WSDLLocalation 特性先取得WSDL 文件,
因此你在圖中可以看到 Client AP 送出一個HTTP Request 給Service Server,這個訊息會經由
TWebModule 轉送給TWSDLHTMLPublish 接著再由TWSDLHTMLPublish 產生WSDL 文件後傳送給Client AP,
這只有在你指定的WSDLLocation 是連結至Service Server 的URL 時才會發生,假使你預先將WSDL 存
放在本機或是第二次呼叫的話,這個動作就不會發生了。
取得WSDL 文件後,HTTPRIO 就會將Request 轉成SOAP Message傳送至Servcie Server,再由TWebModule
轉送給TSOAPDispatcher,接著經由TSOAPDispatcher解讀SOAP Header中的SOAPAction 資訊後轉送給TSOAPPascalInvoker,
這時TSOAPPascalInvoker 會建立一個THTTPSOAPToPasBind 物件並將SOAP Action傳入後取得Service Class Type,
再運用TOPToSoapDomConvert 來解出SOAP Message 中的參數後呼叫對應的Service。
當Service 執行完之後,TOPToSoapDomConvert 就會將Method 的傳回值打包成SOAP Response Message後傳回Client AP。
除了表面上看到的這些之外,InvokeRegistry也扮演著相當重要的角色,因為TSOAPPascalInvoker 必須得經由她來取得
Service Interface 與Service Object,之後才能使用TInterfaceInvoker來呼叫對應的Method。
可惜Borland 並沒有公開Invoker 的原始碼,但我們可以想像其中必定包含許多Compiler 層級的技術,BizSnap 目前
還有一些缺點, 那就是程式設計師無法介入這些步驟! 因為這些元件既沒有事件,也不能繼承,希望Borland以後
能解除這個限制. 除了Service Server 之外,Client AP端的魔法也很有趣,讓我們繼續探索BizSnap Client 的魔法:

 

還記得之前提到的要求WSDL 文件嗎? 在這張圖中我省略掉她了。當你利用HTTPRIO 來呼叫Service Method時,
HTTPRIO 會到InvokeRegistry 取得相關的SoapAction 資訊,再將參數等資訊交由TOPToSoapDomConvert 轉換
為SOAP Request Mesage後經由THTTPReqResp 送至Service Server,回返後再由TOPToSoapDomConvert 轉換
SOAP Response Message 成為Pascal 型態後存入對應的物件,乍看之下 Client 的步驟似乎沒有Server 複雜,
其實不然! 因為還有HTTPRIO 可以轉型成任何型態這個魔法沒解開,Borland 也沒有公開RIO 的原始碼,不過我想其中
必定有許多的magic code,身為程式設計師,我很好奇! 不過我也能了解Borland 不公開的想法,罷了! 這對我們並
沒有太大的影響。
關於魔法的初級篇就到此打住,讓我們繼續往下看吧。
傳送複雜型態資料(Complex Type)
在SOAP 一節中我提到了SOAP 可以傳送複雜的資料,在這裡我們就使用DELPHI
來實作一個可以傳送Complex Type 的WebService。
首先請你開啟Service專案,並啟動New Items,
你可以看到WebService內有一個Complex Type Generate Wizard,請執行她。


接著填入Type Name 跟勾選Generate Dynamic Array,
這會產生Array Type 的定義。


之後她會產生Complex Type 的骨架程式碼unit Unit3;// Created with Thor-Mjollnir Complex-Type Module Creatorinterfaceuses InvokeRegistry,Types,XMLSchema;type
   TXSPerson=class;
   TXSPersonArray = array of TXSPerson;
   TXSPerson = class(TRemotable)
   private
    //declare your data member,remember! data member need publish in published section
    FName:string;
    FAge:Integer;
   { Private declarations }
   published
    property Name:string read Fname write Fname;
    property Age:Integer read Fage write Fage;
   { Public declarations }
  end;implementationinitialization  RemClassRegistry.RegisterXSClass(TXSPerson, ''''http://www.code6421.com/XMLSchema'''', ''''TXSPerson'''','''''''');  RemTypeRegistry.RegisterXSInfo(TypeInfo(TXSPersonArray),''''http://www.code6421.com/XMLSchema'''',''''TXSPersonArray'''');finalization  RemClassRegistry.UnRegisterXSClass(TXSPerson);  RemTypeRegistry.UnRegisterXSInfo(TypeInfo(TXSPersonArray));end.
將她存為uXSPerson.pas,之後我們還要回到Interface,在定義的Unit 中添加新的Method。

unit DPServiceIntf;// Created with Thor-Mjollnir Service Module Creatorinterfaceuses
   InvokeRegistry,uXSPerson;type
   IDPService = interface(IInvokable)
   [''''{F5AF5412-0882-4C69-8878-4775C1D77555}'''']
   function SayHello:string; stdcall;
   function GetPerson:TXSPerson;stdcall;
   end;implementationinitialization InvRegistry.RegisterInterface(TypeInfo(IDPService));
end.

定義好Interface 後,我們還要為這個Method 提供實作碼。unit DPServiceImpl;// Created with Thor-Mjollnir Service Module Creatorinterfaceuses
   InvokeRegistry,DPServiceIntf,uXSPerson;type
   TDPService = class(TInvokableClass,IDPService)
   private
   { Private declarations }
   public
   function SayHello:string; stdcall;
   function GetPerson:TXSPerson;stdcall;
   { Public declarations }
   end;implementationfunction TDPService.SayHello:string;
begin
   Result:=''''Hello!'''';
end;function TDPService.GetPerson:TXSPerson;stdcall;
begin
   Result:=TXSPerson.Create;
   Result.Name:=''''code6421'''';
   Result.Age:=18; //ha!
end;initialization
   InvRegistry.RegisterInvokableClass(TDPService);
 end.
 回到Client 端,請你手動在Service Interface 加入GetPerson function的定義,
這個動作並不困難,你只須要將ComplexType Unit Copy 到你的Client 目錄中並use 她就可以了,
接下來你應該就可以呼叫GetPerson 這個Method 了,基本上你的Complex-Type 中也可以包含
Complex-Type,所以組合後可以表現相當複雜的資料型態,除此之外你也可以產生Complex-Type Array
用以表示更複雜的資料,這些都是很簡單的運用,相信你很快就可以上手了。
以DELPHI 來撰寫Complex Type 可說是相當容易,只是在撰寫Complex Type 時要注意一點,
你必需將所有需要傳送的資料放在published 區,這樣DELPHI 才能由RTTI 取得並轉換成可傳送的格式。
傳送檔案或圖形

如果你要運用SOAP 來傳送檔案的話,你可能得多注意一下了! 因為一旦檔案被轉為XML Array Type 後
大小一定會增加。 再加上編入及取出的動作,速度一定很不理想。以一個14000 Byte 的檔案來說,
就要花掉1,2 分鐘。這對應用程式來說很難接受,因此這裡我介紹兩種處理方法,一種是以TByteDynArray
直接傳遞,另一種則是以MIME 來傳遞,以TByteDnyArray 型別來處理檔案的傳送是相當簡單的技巧,
你只需要寫幾行程式就可以了,但簡單的事總有代價,而這個代價正是效能低落。我們用之前的WebService 為基礎,為她添加檔案傳送的功能,下面是傳送檔案的Service Server 片段程式:
unit DPServiceIntf;// Created with Thor-Mjollnir Service Module Creatorinterfaceuses
   InvokeRegistry,uXSPerson,Types;type
   IDPService = interface(IInvokable)
   ….
   function GetFile:TByteDynArray;stdcall;
   end;   unit DPServiceImpl;
   uses
     InvokeRegistry,DPServiceIntf,uXSPerson,Types,Classes,SysUtils;
   …..
function TDPService.GetFile:TByteDynArray;stdcall;
var
   fs:TFileStream;
   iSize:Int64;
begin
   fs:=TFileStream.Create(''''e:\code\Doc-exam\BizSnap\Exam1\test.jpg'''',fmOpenRead);
   iSize:=fs.Seek(0,soFromEnd);
   SetLength(Result,iSize);
   fs.Seek(0,soFromBeginning);
   fs.ReadBuffer(Result[0],iSize);
   fs.Free;
end;

這段程式使用TFileStream物件讀入一個JPG,再將它填入TByteDynArray 中傳回給Client 端。
接著我們再修改一下呼叫Service 的Client 程式。

procedure TForm1.Button1Click(Sender: TObject);var  vByte:TByteDynArray;  msByte:TMemoryStream;  iSize:Integer;  vImage:TJPEGImage;begin  vByte:=(HTTPRIO1 as IDPService).GetFile;  msByte:=TMemoryStream.Create;  iSize:=High(vByte);  msByte.WriteBuffer(vByte[0],iSize);  msByte.Position:=0;  vImage:=TJPEGImage.Create;  vImage.LoadFromStream(msByte);  Image1.Picture.Graphic:=vImage;  Image1.Refresh;  msByte.Free;end;

當上面的程式執行起來時你會發現速度相當的慢,你可別以為當掉了!
這就是使用ByteArray 來傳遞檔案或循序型資料的必然後果。因此我的第二個例子就是使用MIME
來傳遞資料,要完成這個工作的話,你必需先撰寫一個Complex Type Class,用來處理MIME 資料,
在這個例子中你可以學到另一種Complex Type 的應用,就是當你的資料格式無法以PASCAL
的資料型態來表示時,你可以撰寫成TRemotableXS的延伸類別,並且override NativeToXS,XSToNativeXS 兩個函式,
將資料轉成字串。 當傳送至Client 端後,再將字串還原為原來的格式,我們的MIME 就需要這種處理方式,
這個程式的原作者是Noah Kriegel! 原來的程式裡面使用到他自己的MIME Library,因此我稍稍修改了一下,
把處理MIME 的部份改成使用Indy,由於DELPHI 6 所附的Indy 對MIME 支援還不夠,
因此你要到Indy 的網站去下載最新的9.0,記得多看一下安裝說明,
網址是: http://www.nevrona.com/Indy/download90.html安裝後你就可以開始撰寫處理MIME Type 的Complex Type。

unit B64XSClass;interfaceuses SysUtils, InvokeRegistry,IdBaseComponent, IdComponent, IdCoder,IdCoderMIME,Types;typeEXSBase64Error = class(Exception);TXSBase64 = class(TRemotableXS)
 private
   FBase64String: string;
 protected
   function GetAsString: string;
   procedure SetAsString(Value: string);
 public
   procedure XSToNative(Value: WideString); override;
   function NativeToXS: WideString; override;
   property AsString: string read GetAsString write SetAsString;
   procedure LoadFromFile(const FileName: string);
   procedure SaveToFile(const FileName: string);
 end;implementationuses SoapConst, Windows, Classes, Dialogs;procedure TXSBase64.XSToNative(Value: WideString);
begin
   SetAsString(Value);
end;function TXSBase64.NativeToXS: WideString;
begin
   Result := GetAsString;
end;function TXSBase64.GetAsString: string;
begin
   Result := FBase64String;
end;procedure TXSBase64.SetAsString(Value: string);
begin
   FBase64String := Value;
end;procedure TXSBase64.LoadFromFile(const FileName: string);
var
   InputFile: TFileStream;
   Base64Stream: TStringStream;
   IdEncoderMIME1: TIdEncoderMIME;
   ResultCode:WideString;
begin
  if FileName = '''''''' then
   raise EXSBase64Error.Create(''''No Input File Specified'''');
  if not(FileExists(FileName)) then
   raise EXSBase64Error.CreateFmt( ''''The Specified File, %s''''#10#13''''Does Not Exist'''',  [FileName]);
 try
   InputFile := TFileStream.Create(FileName, fmOpenRead);
   Base64Stream := TStringStream.Create('''''''');
   InputFile.Position := 0;
   Base64Stream.Position := 0;
   IdEncoderMIME1:=TIdEncoderMIME.Create(Nil);
   ResultCode:=IdEncoderMIME1.Encode(InputFile);
   Base64Stream.WriteString(ResultCode);
   IdEncoderMIME1.Free;
   Base64Stream.Position := 0;
   FBase64String := Base64Stream.ReadString(Base64Stream.Size);
 finally
   InputFile.Free;
   Base64Stream.Free;
 end; { try..finally }
end;procedure TXSBase64.SaveToFile(const FileName: string);
 var
   OutputFile: TFileStream;
   Base64Stream: TStringStream;
   IdDecoderMIME1: TIdDecoderMIME;
   ResultCode:WideString;
   iSize:Int64;
begin
   if FileName = '''''''' then
   raise EXSBase64Error.Create(''''No Output File Specified'''');
   if FBase64String = '''''''' then   raise EXSBase64Error.Create(''''This Object does not Contain a''''#10#13+   ''''Base64-Encoded File or String'''');
  try
   Base64Stream := TStringStream.Create(FBase64String);
   OutputFile := TFileStream.Create(FileName, fmCreate);
   OutputFile.Position := 0;
   Base64Stream.Position := 0;
   iSize:=Base64Stream.Seek(0,soFromEnd);
   Base64Stream.Position:=0;
   ResultCode:=Base64Stream.ReadString(iSize);
   IdDecoderMIME1:=TIdDecoderMIME.Create(Nil);
   IdDecoderMIME1.DecodeToStream(ResultCode,OutputFile);
  finally
   IdDecoderMIME1.Free;
   OutputFile.Free;
   Base64Stream.Free;
  end;{ try..finally }
end;initialization
   RemClassRegistry.RegisterXSClass(TXSBase64, XMLSchemaNameSpace, ''''base64'''', '''''''',    True );
finalization
   RemClassRegistry.UnRegisterXSClass(TXSBase64);
end.

將這個Unit 加到我們的WebService 專案中,接著我們在Service 中添加一個Methods:GetFile64
unit DPServiceIntf;// Created with Thor-Mjollnir Service Module Creatorinterfaceuses
   InvokeRegistry,uXSPerson,Types,B64XSClass;type
   IDPService = interface(IInvokable)
   [''''{F5AF5412-0882-4C69-8878-4775C1D77555}'''']
   function SayHello:string; stdcall;
   function GetPerson:TXSPerson;stdcall;
   function GetFile:TByteDynArray;stdcall;
   function GetFile64:TXSBase64;stdcall;
   end;implementationinitialization InvRegistry.RegisterInterface(TypeInfo(IDPService));
end.別忘了還要提供實作哦:unit DPServiceImpl;
   // Created with Thor-Mjollnir Service Module Creatorinterfaceuses
   InvokeRegistry,DPServiceIntf,Unit4,SysUtils,Types,Classes,B64XSClass;type
   TDPService = class(TInvokableClass,IDPService)
   private
   { Private declarations }
   public
   function SayHello:string; stdcall;
   function GetPerson:TXSPerson;stdcall;
   function GetFile:TByteDynArray;stdcall;
   function GetFile64:TXSBase64;stdcall;
   { Public declarations }
   end;implementationfunction TDPService.SayHello:string;
begin
   Result:=''''Hello!'''';
end;function TDPService.GetPerson:TXSPerson;stdcall;
begin
   Result:=TXSPerson.Create;
   Result.Name:=''''code6421'''';
   Result.Age:=18; //ha!
end;function TDPService.GetFile64:TXSBase64;stdcall;
begin
   Result:=TXSBase64.Create;
   Result.LoadFromFile(''''e:\code\Doc-exam\BizSnap\Exam1\test.jpg'''');
end;function TDPService.GetFile:TByteDynArray;stdcall;
var
   fs:TFileStream;
   iSize:Int64;
begin
   fs:=TFileStream.Create(''''e:\code\Doc-exam\BizSnap\Exam1\test.jpg'''',fmOpenRead);
   iSize:=fs.Seek(0,soFromEnd);
   SetLength(Result,iSize);
   fs.Seek(0,soFromBeginning);
   fs.ReadBuffer(Result[0],iSize);
   fs.Free;
end;initialization
   InvRegistry.RegisterInvokableClass(TDPService);
end.

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Dynamically Invoking a SOAP method by name?
Delphi接口
Delphi制作DLL
详解.net中的callback机制和传值跟踪
安装前判断进程是否存在,以及停止相应进程,卸载完成时自动打开网页
delphi AES encrypt
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服