SOAP在PHP里的应用
简介:
简单对象访问协议(SOAP,全写为Simple Object Access Protocol)是一种标准化的通讯规范,主要用于Web服务(web service)中。SOAP的出现是为了简化网页服务器(Web Server)在从XML数据库中提取数据时,无需花时间去格式化页面,并能够让不同应用程序之间透过HTTP通讯协定,以XML格式互相交换彼此的数据,使其与编程语言、平台和硬件无关。
此标准由IBM、Microsoft、UserLand和DevelopMentor在1998年共同提出,并得到IBM,莲花(Lotus),康柏(Compaq)等公司的支持,于2000年提交给万维网联盟(World Wide Web Consortium;W3C),目前 SOAP 1.1 版是业界共同的标准,属于第二代的XML协定(第一代具主要代表性的技术为XML-RPC以及WDDX)。
传输方式:
SOAP使用因特网应用层协议作为其传输协议。SMTP以及HTTP协议都可以用来传输SOAP消息,但是由于HTTP在如今的因特网结构中工作得很好,特别是在网络防火墙下仍然工作流畅,所以其更为广泛地被采纳。SOAP亦可以在HTTPS上进行传输。
PHP-SOAP:
SOAP 提供了一种标准,使得运行在不同平台上并使用不同的编程语言编写的应用程序可以互相进行通信。SOAP 的可扩展性和平台无关性使得它被广泛用作 Web 服务的通信协议。
由于 Java 语言提供了对 SOAP 的良好支持,通常基于 Web 服务的应用程序使用 Java 语言编写。对于广大的 PHP 程序员来说,要重新学习一门语言还是很需要时间的。自PHP 5开始新增了内置的 SOAP扩展 (ext/soap),目前最新的版本 (5.3.0) 中已经提供了比较完整的对 SOAP 的支持,而且我们有理由相信,以后的版本还会更好。
SOAP 扩展库结构
ext/soap 中包括六个预定义的类,通过这些类,我们可以创建 Web 服务端 (SoapServer 类 ),客户端 (SoapClient 类 ),处理 SOAP 请求和应答 (SoapHeader, SoapParam, SoapVar 类 ),诊断错误 (SoapFault 类 )。这些类之间的联系如图 1 所示:
图 1. SOAP 扩展的结构
SOAP 服务类 SoapServer
SoapServer 类用来开发 Web 服务端应用程序。这个类中包含创建,设置和操纵 Web 服务的函数。有两种方式可以向 Web 服务中添加操作 (Operation)。一种方式是直接添加已定义的函数,另一种方式是添加已定义好的类,从而将该类的公有成员函数添加到 Web 服务中。
另一个需要说明的特性是,PHP 支持两种 Web 服务的模式:WSDL 模式和 non-WSDL 模式,为了便于理解,我们首先从 Web 服务的两种实现模式开始说起。
PHP 中 Web 服务的两种模式:WSDL 模式和 non-WSDL 模式
对于 Web 服务来说,主要有两种实现模式 – 契约先行 (Contract First) 模式和代码先行 (Code Fist) 模式。
契约先行模式的实现中,首要工作是定义针对这个 Web 服务的借口的 WSDL(Web Services Description Language,Web 服务描述语言 ) 文件。WSDL 文件中描述了 Web 服务的位置,可提供的操作集,以及其他一些属性。WSDL 文件也就是 Web 服务的“契约”。“契约”订立之后,再据此进行服务器端和客户端的应用程序开发。这种模式对应上节所说的 WSDL 模式。我们后文中介绍的例子就是使用这一模式实现的。
与契约先行模式不同,代码先行模式中,第一步工作是实现 Web 服务端,然后根据服务端的实现,用某种方法(自动生成或手工编写)生成 WSDL 文件。但是由于 PHP 本身并没有提供从 Web 服务实现代码中生成 WSDL 文件的方法,因此就要以 non-WSDL 模式连接服务端,即不通过 WSDL 文件创建 SoapServer 和 SoapClient 示例,而是直接向构造函数传递必要的参数。当然,代码先行模式也有其他的解决方法,一些集成的 PHP 开发工具(如 Zend Studio)就提供了根据 Web 服务实现代码生成 WSDL 文件的功能。
SOAP 客户端类 SoapClient
SOAP 客户端类 SoapClient 用于开发 Web 服务的客户端程序。可用的成员函数主要有创建客户端实例,调用可用操作,查询可用操作和数据类型等。除此之外还包括了可用于程序调试的函数 – 获取上次请求和应答的 SOAP 数据。
SOAP 参数类 SoapHeader, SoapParam, SoapVar
SoapParam 和 SoapVar 主要用来封装用于放入 SOAP 请求中的数据,他们主要在 non-WSDL 模式下使用。事实上,在 WSDL 模式下,SOAP 请求的参数可以通过数组方式包装,SOAP 扩展会根据 WSDL 文件将这个数组转化成为 SOAP 请求中的数据部分,所以并不需要这两个类。而在 non-WSDL 模式下,由于没有提供 WSDL 文件,所以必须通过这两个类进行包装。
SoapHeader 类用来构造 SOAP 头,SOAP 头可以对 SOAP 的能力进行必要的扩展。SOAP 头的一个主要作用就是用于简单的身份认证。
SOAP 异常类 SoapFault
这个类从 PHP 的 Exception 类继承而来,可以用来实现 SOAP 中的异常处理机制,由 SOAP 服务端抛出。SOAP 客户端可以接收该类的实例,用于获取有用的调试信息。
首先我们需要开启SOAP模块。
Linux平台下编译PHP的时候设置参数--enable-soap
在 php.ini 中加入如下代码:
extension = php_soap.so
Window平台下确定ext文件下有php_soap.dll文件
在 php.ini 中加入如下代码:
extension = php_soap.dll
SOAP的基本业务逻辑
本质上 Web 服务端和客户机都是一个相对独立的 Web 应用程序,它们之间只是通过 SOAP 消息进行通信。在不改变通信“契约”的情况下,Web 服务端和客户端内部实现的改变均不影响这个系统的功能。所以对于“契约”- 即 WSDL 文件的定义就是非常重要的一步。
下面我就以一个简单例子来简单说明一下开发流程,这个例子是根据用户名获取用户发的信息.
对这个例子,WSDL文件的图形化表示如下图:
需要创建三个文件
1.server.php(SOAP服务端程序)
2.message.wsdl(WSDL声明文件)
3.client.php(SOAP客户端程序)
首先创建WSDL文件
前面提到过,PHP 本身并没有提供可以自动生成 WSDL 文件的方法,因此就需要我们自己编写 WSDL 文件。WSDL 的结构虽然比较清楚,但完全依靠文本编辑器创建一个 WSDL 文件依然是个艰难的任务。这是因为 WSDL 中的元素比较多,每个元素还有若干属性,要完全掌握这些比较困难。另一方面,如果没有开发环境的辅助,我们在编写 WSDL 文件中的错误就很难被发现,存在任何一个微小的错误(例如标签名 message 误写成了 massage),我们的应用程序也无法正常工作。因此在编写 WSDL 文件时使用适当的开发工具是很必要的。下面我们介绍两种借助开发工具生成 WSDL 文件的方法,一种适用于契约先行模式,另一种适用于代码先行模式。
使用Zend Studio编写WSDL文件
Zend Studio提供了对于PHP开发中各种需求的良好支持。我们可以在菜单中选择 New->Other...,然后在弹出的窗口中选择 Web Service 下的 WSDL File,然后输入文件名,创建WSDL文件,Zend Studio会生成一个默认的WSDL“框架”,并以图形化的方法显示出来.首先创建Service,右键->Add Service
接下来右键->Add PortType
传入参数对应了server.php里面的getMessage方法的参数,可定义多个,名字可以随便起(当然要自己认得), 传入参数按照顺序和函数的参数一一对应,参数的数据类型一定要设置正确,不然不能传值的.
传出参数对应了getMessage方法的返回值,同样数据类型要设置正确…
接下来右键->Add Binding
顾名思义它是用来绑定Service和Port的.右键->Show properties,点击Generate Binding Content…里面可以设置名称和portType和Protocol,这里的portType就应该对应messagePortType. Protocol设置之前为选择的通讯协议。
这里的Binding里面选择刚才在Binding里面设置的名称
这样我们的WSDL就创建成功了.
P.S. 注意如果添加了新的方法,需要重复上面的操作,更新Binding...
下面我再来看看server.php
class Message { public function __construct() { $this->conn = mysql_connect('127.0.0.1','root','123456'); mysql_select_db('soap_demo',$this->conn); } public function getMessage($user) { $where = 'SELECT * FROM `message` WHERE 1'; if($user) { $where .= ' AND user="' . $user .'"'; } $res = mysql_query($where,$this->conn); while($row = mysql_fetch_assoc($res)) { $list[] = $row; } return json_encode($list); } } ini_set("soap.wsdl_cache_enabled", "0"); //禁用WSDL cache $server = new SoapServer("http://{服务端IP}/soap/message.wsdl"); //初始化SOAP SERVER 采用WDSL模式 $server->setClass("Message"); //设置类 $server->addFunction("getMessage"); //添加方法 $server->handle(); //通知SoapServer开始处理Web服务的请求,如果缺少了这一语句,Web 服务就不会被启动。
注释里面已经解释得很清楚了.
客户端的调用client.php
try { $client = new SoapClient("http://localhost/soap/message.wsdl", array('trace'=>1)); $result = $client->getMessage('dingjing'); if($result == 'null') { echo 'No Result'; //抛出异常 } else { print($result); } } catch (SoapFault $e) { echo 'SOAP Error'; //抛出异常 };
之后只要访问 http://localhost/soap
SOAP调试:
通过
echo htmlspecialchars($client->__getLastRequest()) echo htmlspecialchars($client->__getLastResponse())
可以显示SOAP的请求和应答
针对本例:
Request : <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.example.org/demo/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:getMessage><user xsi:type="xsd:string">dingjing</user></ns1:getMessage></SOAP-ENV:Body></SOAP-ENV:Envelope> Response : <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.example.org/demo/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:getMessageResponse><messageReturn xsi:type="xsd:string">[{"id":"1","user":"dingjing","message":"Test Soap"},{"id":"2","user":"dingjing","message":"Test Soap 2"}]</messageReturn></ns1:getMessageResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
可以观察SOAP的传入参数,传出参数是否正确,如果没有获取到值基本上就是参数设置错误的原因了。