项目(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="http://schemas.xmlsoap.org/soap/actor/next"
soapenv:mustUnderstand="0" xsi:type="soapenc:string"
xmlns:ns1="Authorization" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
username
</ns1:username>
<ns2:password
soapenv:actor="http://schemas.xmlsoap.org/soap/actor/next"
soapenv:mustUnderstand="0" xsi:type="soapenc:string"
xmlns:ns2="Authorization" xmlns:soapenc="http://schemas.xmlsoap.org/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]);
//转义字符处理 &lt; 改 <
SOAPData := StringReplace(SOAPData,'&lt;','<',[rfReplaceAll, rfIgnoreCase]);
//转义字符处理 &gt; 改 >
SOAPData := StringReplace(SOAPData,'&gt;','>',[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="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" ' +
' xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">' +
'<SOAP-ENV:Header>' +
'<ns1:username SOAP-ENV:actor="http://schemas.xmlsoap.org/soap/actor/next" SOAP-ENV:mustUnderstand="0" xsi:type="soapenc:string" xmlns:ns1="Authorization" ' +
'xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">' +
':WS_USER_NAME' +
'</ns1:username>' +
'<ns2:password SOAP-ENV:actor="http://schemas.xmlsoap.org/soap/actor/next" SOAP-ENV:mustUnderstand="0" xsi:type="soapenc:string" xmlns:ns2="Authorization" ' +
'xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">' +
':WS_PASSWORD' +
'</ns2:password>' +
'</SOAP-ENV:Header>' +
'<SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' +
'<NS2:invoke xmlns:NS2="http://server.webservice.core.epm">' +
'<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()) + 'log') then
MkDir(ExtractFilePath(ParamStr()) + 'log'); //按日期及时间设定保存日志的文件名
mfile := ExtractFilePath(ParamStr()) + '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 } procedure TPingThread.execute;
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) + ' TPingThread.execute:'+E.Message);
end;
end;
LeaveCriticalSection(PingCS);
end; end.

测试效果,可以发现,发出的报文和接收的报文是一致的:

源码下载:http://files.cnblogs.com/files/tc310/WebServiceDemo.rar

Delphi调用WebService(通过SoapHeader认证)经验总结的更多相关文章

  1. Delphi调用webservice总结

    Delphi调用webservice总结     Delphi调用C#写的webservice 用delphi的THTTPRIO控件调用了c#写的webservice. 下面是我调试时遇到的一些问题: ...

  2. delphi 调用Webservice 引入wsdl 报错 document empty

    delphi 调用Webservice 引入wsdl 报错 document empty 直接引入wsdl 地址报错 document empty 解决办法:在浏览器里保存为xml文件,然后在开发环境 ...

  3. ANDROID调用webservice带soapheader验证

    最近的一个项目中调用webservice接口,需要验证soapheader,现将解决方法记录如下:(网上资料出处太多,就不做引用,原作者如看到,如有必要添加请通知) 1.先看接口 POST /webs ...

  4. 动态调用Webservice 支持Soapheader身份验证(转)

    封装的WebserviceHelp类: using System; using System.CodeDom; using System.CodeDom.Compiler; using System. ...

  5. 【转】Delphi调用webservice总结

    原文:http://www.cnblogs.com/zhangzhifeng/archive/2013/08/15/3259084.html Delphi调用C#写的webservice 用delph ...

  6. Delphi实现WebService带身份认证的数据传输

    WebService使得不同开发工具开发出来的程序可以在网络连通的环境下相互通信,它最大的特点就是标准化(基于XML的一系列标准)带来的跨平台.跨开发工具的通用性,基于HTTP带来的畅通无阻的能力(跨 ...

  7. php的webservice的soapheader认证问题

    参数通过类传输:class authentication_header {       private $username;       private $password;       public ...

  8. delphi调用webservice 转

      如今 Web Service 已越来越火了,在DotNet已开发的Web Service中,Delphi 7如何方便的调用DotNet写的Web Service呢?方法有两种,一种是在Delphi ...

  9. delphi 调用 webservice (.NET C#版)

    uses XMLIntf, XMLDoc; XML to XTR文件转换 .File-->open打开你要分析的XML文件 .在左边选择你要分析的接点,双击加到中间的转换列表中 .Create- ...

随机推荐

  1. jQuery文本框(input textare)事件绑定方法教程

    jquery 的事件绑定已经用on替换了原来的bind,接下来为大家分享下bind的使用方法及input textare事件.目前1.7以上,jquery?的事件绑定已经用on替换了原来的bind,接 ...

  2. 推荐一款C#反编译软件(开源)

    大二的时候老师要求做过一个小项目,大概4个人左右一组.当时交流不是特别到位,项目在一个同学的电脑上建成了就一直在他的电脑上(所以好东西不要烂在你的硬盘里),也不知道什么源码管理,可悲到项目做完我还没有 ...

  3. hadoop 异常及处理总结-01(小马哥-原创)

    试验环境: 本地:MyEclipse 集群:Vmware 11+ 6台 Centos 6.5 Hadoop版本: 2.4.0(配置为自动HA) 试验背景: 在正常测试MapReduce(下简称MR)程 ...

  4. HW7.12

    import java.util.Scanner; public class Solution { public static void main(String[] args) { double[] ...

  5. URAL-1991 The battle near the swamp 水题

    题目链接:http://acm.timus.ru/problem.aspx?space=1&num=1991 水题.. //STATUS:C++_AC_15MS_333KB #include ...

  6. initWithSpriteFrameName和createWithSpriteFrameName

    /** * Initializes a sprite with a sprite frame name. <br/> * A cc.SpriteFrame will be fetched ...

  7. datagrid url json

    <div class="easyui-accordion" style="width:500px;height:300px;"> <div t ...

  8. C++11无限制的unions

    [C++11无限制的unions] 在标准 C++ 中,并非任意的类型都能做为 union 的成员.比方说,带有 non-trivial 构造函数的类型就不能是 union 的成员.在新的标准里,移除 ...

  9. Guid函数

    使用GUID函数可以得到一个不重复的序列号,但是考虑到会出现并发等一系列情况,所以建议使用时间+GUID的方法去生成一串序列号 ,一般语法如下: string str = System.Guid.Ne ...

  10. HDU 1561The more, The Better(树形DP)

    HDU 1561  The more, The Better 题目大意就不说了 直接DP[i][j]表示i为跟节点的子树上攻克j个城堡的所能获得的最多宝物的数量 DP[fa][j] = MAX{DP[ ...