Delphi调⽤WebService(通过SoapHeader认证)经验总结项⽬(Delphi开发)需要调⽤另⼀个系统的WebService。⾛了不少弯路,现记录总结⼀下经验。以下是WebService要求:
1、WebService概述
营销Webservice接⼝采⽤Apache Axis(version 1.4)技术实现。客户端和服务器⽤SOAP(Simple Object Access Protocol)协议通过HTTP来交互,客户端根据WSDL描述⽂档⽣成SOAP请求消息发送到服务端,服务端解析收到的SOAP请求,调⽤Web service,然后再⽣成相应的SOAP应答送回到客户端。
2 、认证机制
营销的所有Webservice服务均需要认证通过(部分需要授权)才能够被调⽤。营销Webservice服务接收到请求后从Soap头中获取⽤户名和密码,进⾏认证,认证通过后再调⽤具体服务。
作为客户端,应⽤程序代码(使⽤Axis的客户端编程模型来编写的)需要将⽤户名和密码设置到SOAPHeader中。SOAPHeaderElement 的namespace约定为Authorization,localPart约定为username 和 password。
根据客户端程序语⾔及调⽤⽅式不同,设置的⽅法也不同,下⾯⽰例说明客户端程序语⾔为java调⽤⽅
式为动态调⽤的设置⽅法:⽤org.apache.axis.client.Call 的addHeader⽅法:
call.addHeader(new SOAPHeaderElement("Authorization","username",username));
call.addHeader(new SOAPHeaderElement("Authorization","password",password));
其他的调⽤⽅式及其他语⾔设置⽅式请查阅Axis相关⽂档。
最终传输的SOAP报⽂格式如下:
最终传输的SOAP头信息如下:
<soapenv:Header>
<ns1:username
soapenv:actor="/soap/actor/next"
soapenv:mustUnderstand="0" xsi:type="soapenc:string"
xmlns:ns1="Authorization" xmlns:soapenc="/soap/encoding/">
username
</ns1:username>
<ns2:password
soapenv:actor="/soap/actor/next"
soapenv:mustUnderstand="0" xsi:type="soapenc:string"
xmlns:ns2="Authorization" xmlns:soapenc="/soap/encoding/">
password
</ns2:password>
</soapenv:Header>
开始的时候,按照⼀般调⽤WebService⽅法进⾏:导⼊wsdl,⾃动⽣成WebService调⽤函数,⼿⼯添加⼀个类继承TSOAPHeader类,使⽤HTTPRIO发送SOAP报⽂。但是使⽤SOAPUI测试发出的报⽂,发现SoapHeader信息和WebService要求的格式不⼀样。
于是想到,在soap报⽂发出前,⼿动将soap报⽂改成WebService要求的格式,即在HTTPRIO的BeforeExecute事件中修改soap报⽂:procedure TForm1.HTTPRIO1BeforeExecute(const MethodName: String;
var SOAPRequest: WideString);
var
head_begin,head_end,head_len: Integer;
SOAPData: WideString;
old_head: WideString;
begin
SOAPData := SOAPRequest;
//替换SOAP头
head_begin := Pos('<SOAP-ENV:Header',SOAPData);
head_end := Pos('</SOAP-ENV:Header>',SOAPData);
head_len := head_end + Length('</SOAP-ENV:Header>') - head_begin;
old_head := Copy(SOAPData,head_begin,head_len);
SOAPData := StringReplace(SOAPData,old_head,NewSoapHeader,[rfReplaceAll, rfIgnoreCase]);
调用webservice服务//转义字符处理 < 改 <
SOAPData := StringReplace(SOAPData,'<','<',[rfReplaceAll, rfIgnoreCase]);
//转义字符处理 > 改 >
SOAPData := StringReplace(SOAPData,'>','>',[rfReplaceAll, rfIgnoreCase]);
SOAPRequest := SOAPData;
Memo2.Clear;
Memo2.Lines.Add(Utf8ToAnsi(SOAPRequest));
end;
但是,⽤SoapUI测试,发现这样修改后发出的报⽂Header没有了,只有Body部分。
仔细研究了⼀下Delphi的Soap相关控件,最终到以下解决⽅法使⽤THTTPReqResp控件直接发送完整的soap报⽂,相关代码如下:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,IniFiles, DB, ADODB, StdCtrls, InvokeRegistry, Rio,
SOAPHTTPClient,GenericServer1, ExtCtrls,ActiveX, SOAPHTTPTrans;
const
SOAP_DATA =
'<?xml version="1.0"?>' +
'<SOAP-ENV:Envelope xmlns:xsi="/2001/XMLSchema-instance" xmlns:xsd="/2001/XMLSchema" ' +
' xmlns:SOAP-ENV="/soap/envelope/" xmlns:SOAP-ENC="/soap/encoding/">' +
'<SOAP-ENV:Header>' +
'<ns1:username SOAP-ENV:actor="/soap/actor/next" SOAP-ENV:mustUnderstand="0" xsi:type="soapenc:string" xmlns:ns1="Authorization" ' + 'xmlns:SOAP-ENC="/soap/encoding/">' +
':WS_USER_NAME' +
'</ns1:username>' +
'<ns2:password SOAP-ENV:actor="/soap/actor/next" SOAP-ENV:mustUnderstand="0" xsi:type="soapenc:string" xmlns:ns2="Authorization" ' + 'xmlns:SOAP-ENC="/soap/encoding/">' +
':WS_PASSWORD' +
'</ns2:password>' +
'</SOAP-ENV:Header>' +
'<SOAP-ENV:Body SOAP-ENV:encodingStyle="/soap/encoding/">' +
'<NS2:invoke xmlns:NS2="pm">' +
'<path xsi:type="xsd:string">:WS_PATH</path>' +
'<methodName xsi:type="xsd:string">:WS_METHOD_NAME</methodName>' +
'<dataXmlStr xsi:type="xsd:string">' +
'<![CDATA[' +
':WS_XML_DATA' +
']]>' +
'</dataXmlStr>' +
'</NS2:invoke>' +
'</SOAP-ENV:Body>' +
'</SOAP-ENV:Envelope>';
type
TForm1 = class(TForm)
ADOConnection1: TADOConnection;
GroupBox8: TGroupBox;
Label21: TLabel;
Label22: TLabel;
Label23: TLabel;
Label24: TLabel;
Label25: TLabel;
edt_wsdl_url: TEdit;
edt_path: TEdit;
edt_method: TEdit;
edt_user: TEdit;
edt_password: TEdit;
Button1: TButton;
Memo1: TMemo;
Label1: TLabel;
Label2: TLabel;
Memo2: TMemo;
Timer_Ping: TTimer;
HTTPReqResp1: THTTPReqResp;
procedure FormCreate(Sender: TObject);procedure Button1Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Timer_PingTimer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
NETWORK_ID, NETWORK_NAME, WSDL_URL, USER_NAME, PASSWORD, METHOD_NAME, PATH: WideString;
dataXmlStr: WideString;
NewSoapData: WideString;
procedure sendData();
end;
{ 使⽤线程发送WebService }
TPingThread = class(TThread)
protected
procedure execute; override;
end;
procedure write_log(str: string);//写⼊记录⽂件
var
Form1: TForm1;
{ 初始化临界区CS变量 }
PingCS:TRTLCriticalSection;
implementation
uses util_utf8;
{$R *.dfm}
procedure write_log(str: string);
var
F: TextFile;
mfile: string;
begin
try
//判断保存⽇志⽂件的⽬录是否存在
if not DirectoryExists(ExtractFilePath(ParamStr(0)) + 'log') then
MkDir(ExtractFilePath(ParamStr(0)) + 'log');
//按⽇期及时间设定保存⽇志的⽂件名
mfile := ExtractFilePath(ParamStr(0)) + 'log\' + formatdatetime('yyyy-mm-dd', now) + '.txt';
AssignFile(F,mfile);
if not FileExists(mfile) then
Rewrite(F);//如果⽂件不存在,则创建⼀个新的⽂件,并写⼊
Append(F); //追加写⼊
Writeln(F,str);//写⼊并换⾏
CloseFile(F);
except
end;
end;
//读txt
Procedure ReadTxt(FileName:String);
Var
F:Textfile;
str: String;
Begin
AssignFile(F, FileName); {将⽂件名与变量 F 关联}
Reset(F); {打开并读取⽂件 F }
while not Eof(F) do
begin
Readln(F, str);
Form1.Memo2.Lines.Add(str);
end;
ShowMessage(str);
Closefile(F); {关闭⽂件 F}
End;
procedure TForm1.FormCreate(Sender: TObject);
begin
InitializeCriticalSection(PingCS);
end;
//发送
procedure TForm1.Button1Click(Sender: TObject);
begin
Timer_Ping.Enabled := True;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
{ 清除线程CS变量 }
DeleteCriticalSection(PingCS);
end;
procedure TForm1.Timer_PingTimer(Sender: TObject);
begin
{ 创建线程, 向LED屏发送数据 }
TPingThread.Create(False);
end;
procedure TForm1.sendData;
var
svc: GenericServer;
tmpstr: string;
strSend: TStringStream;
begin
WSDL_URL := Trim(edt_wsdl_url.Text);
USER_NAME := Trim(edt_user.Text);
PASSWORD := Trim(edt_password.Text);
METHOD_NAME := Trim(edt_method.Text);
PATH := Trim(edt_path.Text);
dataXmlStr := Trim(Memo1.Text);
//获取⾃定义soap报⽂
NewSoapData := SOAP_DATA;
NewSoapData := StringReplace(NewSoapData,':WS_USER_NAME',USER_NAME,[rfReplaceAll, rfIgnoreCase]);
NewSoapData := StringReplace(NewSoapData,':WS_PASSWORD',PASSWORD,[rfReplaceAll, rfIgnoreCase]);
NewSoapData := StringReplace(NewSoapData,':WS_PATH',PATH,[rfReplaceAll, rfIgnoreCase]);
NewSoapData := StringReplace(NewSoapData,':WS_METHOD_NAME',METHOD_NAME,[rfReplaceAll, rfIgnoreCase]); NewSoapData := StringReplace(NewSoapData,':WS_XML_DATA',dataXmlStr,[rfReplaceAll, rfIgnoreCase]);
Memo2.Text := NewSoapData;
//使⽤HTTPReqResp1控件进⾏发送soap报⽂,不适⽤HTTPRIO控件(发出的报⽂xml会被转义,也不需要导⼊wsdl了) CoInitialize(nil); //线程中使⽤必须加上CoInitialize(nil)和CoUninitilize(),单元中要uses activex。
//将string转换成stream
strSend := TStringStream.Create(NewSoapData);
try
try //加上try。。except,不要弹出爆粗提⽰
HTTPReqResp1.URL := WSDL_URL;
HTTPReqResp1.Send(strSend);
except
on e:Exception do
begin
write_log(FormatDateTime('yyyy-mm-dd hh:nn:ss',Now) + ' 调⽤WebService时发⽣异常,错误原因:'+E.Message);
end;
end;
finally
strSend.Free;
couninitialize;
end
end;
{ TPingThread }
ute;
begin
Form1.Timer_Ping.Enabled :=false;
FreeOnTerminate := True;
{线程临界区代码块开始}
EnterCriticalSection(PingCS);
try
form1.sendData;
{线程临界区代码块结束}
except
on e:Exception do
begin
write_log(FormatDateTime('yyyy-mm-dd hh:nn:ss',Now) + ' ute:'+E.Message);
end;
end;
LeaveCriticalSection(PingCS);
end;
end.
测试效果,可以发现,发出的报⽂和接收的报⽂是⼀致的:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论