使用 PHP 实现 SOAP Web 服务时出错

Errors while implementing SOAP Web service with PHP

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

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

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

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

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

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

前段时间我不得不实施 WSSE 认证标准。我将从 WSSE 标准中提取一些部分用于此示例。

传入的请求有一个 header 看起来像这样...

<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,它在实际函数执行之前执行。

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);
        }
    }
}

那就是装饰器 class。看起来很简单,嗯?装饰器包含一个 class,其名称类似于接收到的请求的 xml header 的第一个 child。每次我们收到 soap 服务器的请求时,都会自动执行此方法。除此之外,装饰器还会检查被调用的 soap 服务器函数是否可用。如果不是,则抛出消费者端的 soap 客户端接收到的 soap 故障。如果存在一种方法也很容易。我们在 class.

中放入的每个 Web 服务方法
class SimpleWebservice
{
    public function doSomeCoolStuff($withCoolParams) : \SoapVar
    {
        // do some fancy stuff here and return a SoapVar object as response
    }
}

为了便于说明,我们的网络服务只有这一项功能。

但是我们到底如何让装饰器与 soap 服务器一起工作?

放轻松,伙计。 SoapServer class 有一些非常棘手的功能,没有记录。 class 有一个名为 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 class,使用 setObject 方法添加装饰器,使用 handle 方法添加装饰器 运行。 soap 服务器接收所有请求,并且在调用 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(...));

结论

在面向 object 的上下文中工作时,SoapServerSoapClient classes 非常酷。因为文档并没有真正给出关于这两个 classes 的太多信息,所以你必须测试和学习。当您知道如何操作时,您可以轻松地创建 SOAP Web 服务。不将任何 xml 写成字符串。

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

有问题吗?