1. soap编码问题
soap采用utf-8编码作为传输的字符集编码,至少php的soap的实现是写死成utf-8字符编码的,如果server端或client端使用的是非utf-8的字符编码时,可能存在转码问题,导致转码错误; 一个不太正规的做法是: 在server端和client端都对字符做url编码(或别的编码),是的传输的字符都转换成了单字节的ascii字符,这样就不会出现转码问题了。 但是,如果访问量很大,会存在流量和效率方面的问题,如果访问量很小,还是可以使用的
2. 让多个类提供服务的方法,如:
1 2 3 4 5 |
$server = new SoapServer(); $class = $_GET["classname"]; $server->setClass("$class"); $server->handle(); |
3. 错误处理
server端的错误如何告知client呢?通过 $server->fault() 来实现,如:
1 2 3 4 5 6 7 8 |
$server = new SoapServer(); try{ $server->setClass("Phpor"); $server->handle(); }catch(Exception $e) { $server->fault($e->getCode(), $e->getMessage()); } |
client 端实现:
1 2 3 4 5 6 7 |
<?php $client = new SoapClient(...); try { $client->test(); } catch(SoapFault $e) { throw new Exception($e->faultstring, $e->faultcode); } |
1) 根据 ext/soap/soap.c 中关于$server->handle() 的实现来看,如果使用的是ZEND_ENGINE_2 ,则在执行的函数中 throw new SoapFault(…) 也会有和上述相同的效果的,如:
1 2 3 4 5 6 7 8 9 |
$server = new SoapServer(); $server->addFunction("Phpor"); $server->handle(); function Phpor() { // 注意,如果是一个别的异常,如Exception ,将不会自动转换为一个有效的soap错误,而是返回500错误,而且没有任何soap的xml信息,如果是一个SoapFault的子类或许是可以的,当然,似乎也没这么做的必要 throw new SoapFault("Server", "something wrong"); } |
可以通过:
# php -i | grep -i engine
来确认使用的Zend Engine 的版本
2) 虽然soapclient可以识别一些http的状态码,但是soapserver:fault 产生错误时,http状态码统一为500,注意,当user-agent 为 Shockwave Flash 时,依然使用200 的状态码返回,大概是对于flash实现soap时特殊情况的兼容吧。参看php: ext/soap/soap.c 中 函数: soap_server_fault_ex(…) 的实现,php的soapclient可以通过如下方式设置user-agent:
1 2 |
<?php $client = new SoapClient("",array("user_agent"=>"agent_name")); |
4. 对象的持久化
对象的持久化就是在session中保存类或对象的一些数据信息;要知道连接句柄等和进程相关的资源信息是无法有效地保存的
5. 数据的传递
如果你执行的server端的函数为 echo “hello world”; 那么client将得不到该信息,参看php的soap扩展中soap.c 有相关代码如下:
1 2 3 4 5 |
... if (php_start_ob_buffer(NULL, 0, 0 TSRMLS_CC) != SUCCESS) { php_error_docref(NULL TSRMLS_CC, E_ERROR,"ob_start failed"); } ... |
6. 什么叫classmap?
在使用SoapClient时,允许通过如下方式设置classmap,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?php $c = new SoapClient(null, array( "location"=>"http://phpor.net/soap.php", "uri"=>"http://phpor.net/", "classmap"=>array("Struct"=>"phpor") )); $phpor = $c->test(); var_dump($phpor); echo $phpor->get_name(); class phpor{ public function get_name() { return $this->name; } } |
因为在server端,如果return一个server端的对象,则被转换为一个Struct的类型,内部含有该对象的一些数据信息,当然,对象的方法信息都是没有的; 该类在client端可能就是不存在的,这时候就可以map到client端定义的一个类,而且可以通过该类的方法来访问对象内部的一些信息。
但是,有一点需要注意,client端不要定义和server端的类相同的属性信息,如果定义了,则该属性将成为一个数组,包含client端和server端的信息。不知道为什么这么设计。
7. 什么叫typemap?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?php $c = new SoapClient(null, array( "location"=>"http://phpor.net/soap.php", "uri"=>"http://phpor.net/", "typemap"=>array( array( "type_name"=>"test", "type_ns"=>"http://phpor.net/", "from_xml"=>"from_xml", "to_xml"=>"to_xml", ) ), )); //... |
8. 关于soap的测试,单个脚本实现的SoapServer和SoapClient:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<?php function Add($x,$y) { return $x+$y; } class LocalSoapClient extends SoapClient { function __construct($wsdl, $options) { parent::__construct($wsdl, $options); $this->server = new SoapServer($wsdl, $options); $this->server->addFunction('Add'); } function __doRequest($request, $location, $action, $version, $one_way = 0) { ob_start(); $this->server->handle($request); $response = ob_get_contents(); ob_end_clean(); return $response; } } $x = new LocalSoapClient(NULL,array('location'=>'test://', 'uri'=>'http://testuri.org')); var_dump($x->Add(3,4)); |
脚本来自: http://us1.php.net/manual/en/soapclient.dorequest.php
9. 关于数据类型
函数的参数和返回值是涉及到数据类型的,如果仅仅使用string、int、array,则基本是没有问题的,如果使用到了对象,就需要注意了,可能需要用到classmap、typemap了,还有SoapParam、SoapVar; 稍后再研究
10. 关于location和uri
在new SoapClient和new SoapServer时都需要uri,而且这两个uri也是可以不一样的,其实程序根本不会去访问这个uri,这个uri只是xml中的一个名字空间的名字; 而new SoapClient时的那个location却是真正要访问的soapserver的url地址;但是,需要注意的是: uri 最好不要带参数,带一个参数还好,如果要带多个,必然出现 ‘&’,而uri是要写到请求的xml中的,xml中出现 ‘&’,会被解释为实体,然而却不是一个合法的实体,所以就会出现 400 错误,然后就是调试半天,百思不得姐。