Errors while implementing SOAP Web service with PH

2019-08-11 09:23发布

I have to implement a SOAP Web Service using PHP.

I did it by using the SoapServer class and all works fine.

I need to use a specific format for the request: they have to contain a "Header" tag with an "Authentication" tag in which there is a token that I have to use to authenticate the client that performed the request.

I used "file_get_contents('php //input')" to get the entire request that I received and then parsed it to retrieve the token that I needed.

This works fine if I try to simulate a SOAP request by using SoapUI. But, if I try to do the request by using PHP SoapClient and use the function SoapHeader to set the header, on the server side "file_get_contents('php //input')" returns only the fields of the entire request (contained in the XML tags of the XML request) merged together in a string, instead of returning the entire XML in a string format. I cannot understand why.

1条回答
看我几分像从前
2楼-- · 2019-08-11 09:42

The SoapServer class isn 't well documented in the PHP documentation. The SoapServer class does everything that you have in mind completely automatically. You have to use a decorator class. What a decorator is and what it does I 'll explain in the next lines. I 'm trying to give you a push in the right direction.

A while ago I had to implement the WSSE authentication standard. I 'll take some parts from the WSSE standard for this example.

The incoming request had a header that looked like this ...

<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>

The key (identifier) identifies an authorized user to perform a function of the web service. In this sense, we must check that the key is valid before executing any function. For this purpose we need a decorator class, that is executed before the actual function is executed.

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

That 's the decorator class. Looks easy, hm? The decorator contains a class named like the first child of the xml header of the recieved request. This method will be executed automatically every time we recieve a request with the soap server. Beside that the decorator checks, if the called soap server function is available. If not a soap fault is thrown that the soap client on the consumer side recieves. If a method exists is quite easy, too. Every webservice method we put in a class.

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

For illustrative purposes, our web service just has this one function.

But how the hell we bring the decorator to work with the soap server?

Easy, mate. The SoapServer class has some pretty tricky functionality, that is not documented. The class has a method called setObject. This method will do the trick.

$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();

That 's awesome, right? Just initializing the SoapServer class, add the decorator with the setObject method and run it with the handle method. The soap server recieves all requests and before calling the webservice method the decorator will check, if the identifier is valid. Only if the identifier is valid, the called webservice method will be executed.

How 's the soap client request looking?

On the other side the soap client can look like this ...

$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(...));

Conclusion

When working in an object orientated context the SoapServer and SoapClient classes are pretty cool. Because the documentation doesn 't really give much about both classes, you have to test and learn. You can easily create a SOAP webservice when you know how. Without writing any xml as a string.

Before you productively use the code examples seen here, please make sure that they are only examples and not intended for productive use. The shown examples should push you in the right direction. ;)

Questions?

查看更多
登录 后发表回答