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="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(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 }

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

时间: 2024-10-12 22:28:25

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

ANDROID调用webservice带soapheader验证

最近的一个项目中调用webservice接口,需要验证soapheader,现将解决方法记录如下:(网上资料出处太多,就不做引用,原作者如看到,如有必要添加请通知) 1.先看接口 POST /webserver/ValideWebService.asmx HTTP/1.1 Host: IP地址 Content-Type: text/xml; charset=utf-8 Content-Length: length SOAPAction: "http://命名空间/Login" <

【转】Delphi调用webservice总结

原文:http://www.cnblogs.com/zhangzhifeng/archive/2013/08/15/3259084.html Delphi调用C#写的webservice 用delphi的THTTPRIO控件调用了c#写的webservice. 下面是我调试时遇到的一些问题: 1:导入wsdl文件:file--new----other----webservice---WSDLimporter---输入wsdl地址:如下:http://127.0.0.1/ WebService/W

Delphi调用webservice总结

Delphi调用C#写的webservice 用delphi的THTTPRIO控件调用了c#写的webservice. 下面是我调试时遇到的一些问题: 1:导入wsdl文件:file--new----other----webservice---WSDLimporter---输入wsdl地址:如下:http://127.0.0.1/ WebService/WebServiceCall.asmx?wsdl 注意末尾的:'?wsdl'不能少.要不可能会说找不到. 2:设置THTTPRIO控件的属性:

delphi 调用 webservice (.NET C#版)

1 uses XMLIntf, XMLDoc; 2 3 4 5 XML to XTR文件转换 6 7 8 9 1.File-->open打开你要分析的XML文件 10 2.在左边选择你要分析的接点,双击加到中间的转换列表中 11 3.Create-->DataPacket from XML 12 4.Create and Test Transformation 13 5.file-->save-->Transformation得到一个.xtr的文件 14 6.打开.xtr文件,第一

WebService基于SoapHeader实现安全认证

本文仅提供通过设置SoapHeader来控制非法用户对WebService的调用,如果是WebService建议使用WSE3.0来保护Web服 务,如果使用的是Viaual Studio 2008可以使用WCF,WCF里面提供了更多的服务认证方法.以下提供一种基于SoapHeader的自定义验证方式. 1.首先要自定义SoapHeader,须继承System.Web.Services.Protocols.SoapHeader . using System; using System.Collec

delphi 调用百度地图WEBSERVICE转换GPS坐标

百度地图的API说明 使用方法 第一步,申请密钥(ak),作为访问服务的依据: 第二步,按照请求参数说明拼写发送http请求的url,注意需使用第一步申请的ak: 第三步,接收返回的数据(json或者xml格式). 注:本接口支持回调. 服务地址 http://api.map.baidu.com/geoconv/v1/? 组成说明: 域名:http://api.map.baidu.com 服务名:geoconv 服务版本号:v1 服务参数说明 参数 含义 取值范围 是否必须 默认取值 coord

delphi调用 java 的 WebService服务端.

// InvRegistry.RegisterInvokeOptions(TypeInfo(ModelADServicePortType), ioLiteral); InvRegistry.RegisterInvokeOptions(TypeInfo(ModelADServicePortType), ioDocument); delphi调用 java 的 WebService服务端.,布布扣,bubuko.com

(转)Delphi 调用C#编写的WebService 参数为Null解决方法 附中文乱码问题

(转)Delphi 调用C#编写的WebService 参数为Null解决方法 附中文乱码问题 //add-------to support UTF-8     RIO.HTTPWebNode.UseUTF8InHeader := true;  //添加该行,指定采用UTF-8代码传输     RIO.Converter.Encoding:='UTF-8';     RIO.Converter.Options:=RIO.Converter.Options + [soUTF8InHeader,so

?Delphi开发WebService给ASP.NET调用

Delphi开发WebService给ASP.NET调用 说明: 1.本文档分3个部分,分别为: 用Delphi编写WebService 在IIS下部署WebService 在ASP.NET中调用webservice 2.开发环境为Windows XP SP2.Delphi 7.0.IIS 5.1.VS 2008(C#) 一.用Delphi编写WebService 第1步:File → New → Other → WebServices → SOAP Server Application 第2步