【问题标题】:Errors while implementing SOAP Web service with PHP使用 PHP 实现 SOAP Web 服务时出错
【发布时间】:2018-08-02 16:04:19
【问题描述】:

我必须使用 PHP 实现一个 SOAP Web 服务。

我使用SoapServer 类做到了,一切正常。

我需要为请求使用特定格式:它们必须包含一个带有"Authentication" 标记的"Header" 标记,其中有一个令牌,我必须使用它来验证执行请求的客户端。

我使用"file_get_contents('php //input')" 来获取我收到的整个请求,然后对其进行解析以检索我需要的令牌。

如果我尝试使用 SoapUI 模拟 SOAP 请求,这会很好。但是,如果我尝试使用 PHP SoapClient 执行请求并使用函数 SoapHeader 设置标头,则在服务器端 "file_get_contents('php //input')" 仅返回整个请求的字段(包含在 XML 请求的 XML 标记中) 合并成一个字符串,而不是以字符串格式返回整个 XML。 我不明白为什么。

【问题讨论】:

    标签: php soap soapserver


    【解决方案1】:

    SoapServer 类在 PHP 文档中没有详细记录。 SoapServer 类完全自动地完成您想到的所有事情。您必须使用装饰器类。装饰器是什么以及它的作用我将在接下来的几行中解释。我正在努力推动你朝着正确的方向前进。

    不久前,我不得不实现 WSSE 身份验证标准。对于这个例子,我将从 WSSE 标准中提取一些部分。

    传入请求的标头看起来像这样...

    <soapenv:Header>
        <wsse:Security xmlns:wsc="http://schemas.xmlsoap.org/ws/2005/02/sc" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
            <wsc:SecurityContextToken>
                <wsc:Identifier>identifier</wsc:Identifier>
            </wsc:SecurityContextToken>
        </wsse:Security>
    </soapenv:Header>
    

    密钥(标识符)标识授权用户执行 Web 服务的功能。从这个意义上说,我们必须在执行任何功能之前检查密钥是否有效。为此,我们需要一个装饰器类,它在实际函数执行之前执行。

    class AuthDecorator 
    {
        /**
         * Name of the class, which contains the webservice methods
         * @var string
         */
        protected $class;
    
        /**
         * Flag, if the recieved identifier is valid
         * @var boolean
         */
        protected $isValid = false;
    
        public function getClass() : string
        {
            return $this->class;
        }
    
        public function setClass($class) : AuthDecorator
        {
            $this->class = $class;
            return $this;
        }
    
        public function getIsValid() : bool
        {
            return $this->isValid;
        }
    
        public function setIsValid(bool $isValid) : AuthDecorator
        {
            $this->isValid = $isValid;
            return $this;
        }
    
        public function __call(string $method, array $arguments) 
        {
            if (!method_exists($this->class, $method)) {
                throw new \SoapFault(
                    'Server',
                    sprintf(
                        'The method %s does not exist.',
                        $method
                    )
                );
            }
    
            if (!$this->getIsValid()) {
                // return a status object here, wenn identifier is invalid
            }
    
            return call_user_func_array(
                [ $this->class, $method ], 
                $arguments
            );
        }
    
        /**
         * Here 's the magic! Method is called automatically with every recieved request
         *
         * @param object $security Security node form xml request header
         */
        public function Security($security) : void
        {
            // auth against session or database or whatever here
            $identifier = $this->getIdentifierFromSomewhereFunc();
            if ($security->SecurityContextToken->Identifier == $identfier) {
                $this->setIsValid(true);
            }
        }
    }
    

    这就是装饰器类。看起来很简单,嗯?装饰器包含一个类,其名称类似于接收到的请求的 xml 标头的第一个子项。每次我们收到与肥皂服务器的请求时,都会自动执行此方法。除此之外,装饰器检查调用的soap服务器功能是否可用。如果没有引发消费者方的肥皂客户端收到的肥皂故障。如果存在方法也很容易。我们放入一个类中的每个 Web 服务方法。

    class SimpleWebservice
    {
        public function doSomeCoolStuff($withCoolParams) : \SoapVar
        {
            // do some fancy stuff here and return a SoapVar object as response
        }
    }
    

    出于说明目的,我们的网络服务只有这一项功能。

    但是我们到底是怎么让装饰器与肥皂服务器一起工作的呢?

    简单,伙计。 SoapServer 类有一些非常棘手的功能,没有记录。该类有一个名为setObject 的方法。这种方法可以解决问题。

    $server = new \SoapServer(
        $path_to_wsdl_file,
        [
            'encoding' => 'UTF-8',
            'send_errors' => true,
            'soap_version' => SOAP_1_2,
        ]
    );
    
    $decorator = new AuthDecorator();
    $decorator->setClass(SimpleWebservice::class);
    
    $server->setObject($decorator);
    $server->handle();
    

    这太棒了,对吧?只需初始化SoapServer 类,使用setObject 方法添加装饰器并使用handle 方法运行它。肥皂服务器接收所有请求,在调用 webservice 方法之前,装饰器将检查标识符是否有效。只有当标识符有效时,才会执行被调用的webservice方法。

    soap 客户端请求看起来如何?

    另一方面,soap 客户端可能看起来像这样......

    $client = new SoapClient(
        $path_to_wsdl_file,
        [
            'cache_wsdl'    => WSDL_CACHE_NONE,
            'compression'   => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP,
            'exceptions' => true,
            'trace' => true,
        ]
    );
    
    $securityContextToken = new \stdClass();
    $securityContextToken->Identifier = 'identifier';
    
    $securityContextToken = new \SoapVar(
        $securityContextToken,
        SOAP_ENC_OBJ,
        null,
        null,
        'SecurityContextToken',
        'http://schemas.xmlsoap.org/ws/2005/02/sc'
    );
    
    $security = new stdClass();
    $security->SecurityContextToken = $securityContextToken;
    
    $security = new \SoapVar(
        $security, 
        SOAP_ENC_OBJ, 
        null, 
        null, 
        'Security', 
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'
    );
    
    $header = new \SoapHeader(
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd', 
        'Security', 
        $security
    );
    
    $client->__setSoapHeaders($header);
    $result = $client->doSomeCoolStuff(new \SoapParam(...));
    

    结论

    在面向对象的上下文中工作时,SoapServerSoapClient 类非常酷。因为文档并没有真正提供关于这两个类的太多信息,所以您必须进行测试和学习。当您知道如何创建 SOAP Web 服务时,您可以轻松地创建它。无需将任何 xml 写入字符串。

    在您有效地使用此处看到的代码示例之前,请确保它们只是示例而不是用于生产用途。所示示例应将您推向正确的方向。 ;)

    问题?

    【讨论】:

    • 感谢您超级完整的回复!现在一切正常。我现在可以在执行 SOAP Server 的特定功能之前处理带有身份验证参数的 XML 请求的标头。
    • 我还有一个相关的问题。我的 SoapServer 现在工作正常,但它通过在字段名称前使用 env: 和 ns1: 标记来响应。我必须在 xml 响应的标签中复制另一个使用 soap: 并且不使用 ns1: 的 WebService 的行为。我有办法做到这一点吗?
    • 转眼间,我想不出解决办法。我知道如何使用 SoapClient 更改请求和响应。但我不知道 SoapServer 的任何解决方案。也许在服务器端生成带有所有命名空间前缀的整个 xml,而不是 SoapVar 实例?但这不是 SoapServer 类的意义。
    • 与此参数相关的另一个问题:当我返回 SoapFault 时,“Text”标签中缺少“lang”参数。如果我尝试使用 SoapUI 的测试套件,它会返回给我一个模式合规性错误,因此。是否可以将该参数添加到 SoapFault 响应的“文本”标签中?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-21
    • 1970-01-01
    相关资源
    最近更新 更多