用PHP实现SOAP Web服务时出错

时间:2018-08-02 16:04:19

标签: php soap soapserver

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

我通过使用SoapServer类来做到这一点,并且一切正常。

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

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

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

1 个答案:

答案 0 :(得分:1)

PHP文档中没有很好地记录SoapServer类。 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服务器的请求时,该方法就会自动执行。装饰者除了检查被调用的soap服务器功能是否可用之外。如果不是,则抛出肥皂故障,表明消费者方的肥皂客户收到了。如果存在一种方法也很容易。我们将每个webservice方法放入一个类中。

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

出于说明目的,我们的Web服务仅具有此功能。

但是我们到底如何将装饰器与soap服务器一起使用?

容易,伙伴。 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方法。

肥皂客户端请求的外观如何?

在另一方面,肥皂客户端看起来像这样...

$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编写为字符串。

在有效率地使用此处显示的代码示例之前,请确保它们仅是示例,并非用于生产用途。所显示的示例应将您推向正确的方向。 ;)

问题?