PHP 中的 SOAP 错误:OperationFormatter 遇到无效的消息正文
SOAP Error in PHP: OperationFormatter encountered an invalid Message body
原问题:
我正在尝试从 SOAP API wsdl
link 获取数据。我的代码如下。但是我得到了这个错误。任何人都可以帮忙吗?
Error Message: OperationFormatter encountered an invalid Message body.
Expected to find node type 'Element' with name 'GetLocalRates' and
namespace 'http://tempuri.org/'. Found node type 'Element' with name
'soapenv:Envelope' and namespace
'http://www.w3.org/2003/05/soap-envelope'
<?php
$api_link = 'https://www.my-api-link.com/RateAPI.svc/SSL?wsdl';
//setting xml request to api
$request = '<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope" xmlns:tem="http://tempuri.org/" xmlns:ezr="http://schemas.datacontract.org/2004/07/EzremitAPI.Entities">
<soapenv:Body>
<tem:GetLocalRates>
<tem:credentials>
<ezr:AgentCode>####</ezr:AgentCode>
<ezr:HashedPassword>####</ezr:HashedPassword>
<ezr:Username>####</ezr:Username>
</tem:credentials>
<tem:payincurcode>####</tem:payincurcode>
<tem:transferType>####</tem:transferType>
</tem:GetLocalRates>
</soapenv:Body>
</soapenv:Envelope>';
try {
$client = new SoapClient($api_link, array('cache_wsdl' => WSDL_CACHE_NONE, 'soap_version' => SOAP_1_2, 'reliable' => 1.2 , 'useWSA' => TRUE ) );
$soapaction = "http://tempuri.org/IRateAPI/GetLocalRates";
$client->soap_defencoding = 'UTF-8';
// Apply WSA headers
$headers = array();
$headers[] = new SoapHeader('http://www.w3.org/2005/08/addressing', 'To', 'https://www.my-api-link.com/RateAPI.svc/SSL?wsdl', true);
$headers[] = new SoapHeader('http://www.w3.org/2005/08/addressing', 'Action', 'http://tempuri.org/IRateAPI/GetLocalRates', true);
$client->__setSoapHeaders($headers);
$response = $client->GetLocalRates(new SoapVar($request, XSD_ANYXML));
print_r($response);
}
catch(Exception $e) {
echo $e->getMessage();
}
?>
编辑 1(根据第一条评论修改代码)
结果:
http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher/faults:Receivera:InternalServiceFaultOperationFormatter encountered an invalid Message body. Expected to find node type 'Element' with name 'GetLocalRates' and namespace 'http://tempuri.org/'. Found node type 'Element' with name 'soapenv:Envelope' and namespace 'http://www.w3.org/2003/05/soap-envelope'OperationFormatter encountered an invalid Message body. Expected to find node type 'Element' with name 'GetLocalRates' and namespace 'http://tempuri.org/'. Found node type 'Element' with name 'soapenv:Envelope' and namespace 'http://www.w3.org/2003/05/soap-envelope' at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeBody(XmlDictionaryReader reader, MessageVersion version, String action, MessageDescription messageDescription, Object[] parameters, Boolean isRequest)
at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeBodyContents(Message message, Object[] parameters, Boolean isRequest)
at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeRequest(Message message, Object[] parameters)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.DeserializeInputs(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)System.Runtime.Serialization.SerializationException
编辑 2:
$client->__getTypes() 结果:
array(8) {
[0]=>
string(84) "struct EzCredential {
string AgentCode;
string HashedPassword;
string Username;
}"
[1]=>
string(58) "struct ArrayOfCurrencyRate {
CurrencyRate CurrencyRate;
}"
[2]=>
string(187) "struct CurrencyRate {
decimal AgentMargin;
string CurrencyCode;
string CurrencyDescription;
decimal FromAmount;
decimal Rate;
string RateType;
decimal ToAmount;
string Trantype;
}"
[3]=>
string(95) "struct GetLocalRates {
EzCredential credentials;
string payincurcode;
string transferType;
}"
[4]=>
string(74) "struct GetLocalRatesResponse {
ArrayOfCurrencyRate GetLocalRatesResult;
}"
[5]=>
string(8) "int char"
[6]=>
string(17) "duration duration"
[7]=>
string(11) "string guid"
}
$client->__getFunctions() 结果:
array(1) {
[0]=>
string(62) "GetLocalRatesResponse GetLocalRates(GetLocalRates $parameters)"
}
固定:下面使用而不是 XML 信封。非常感谢,@Marcel。你是一个伟大的救世主。
$requestParams = array( 'credentials' => array('AgentCode' => $acode,
'HashedPassword' => $hpass,
'Username' => $uname),
'payincurcode' => $ccode,
'transferType' => $ttype
);
$response = $client->GetLocalRates( $requestParams );
预先感谢您用缺失的数据更新了您的问题。此示例是本机 php 示例,展示了如何以面向对象的方式使用 soap 函数和类型。
1.类型和 classes
如您在类型数组中所见,有几种类型声明为 struct。如果你喜欢这么说,structs 可以被称为 PHP classes。因此,让我们从每个结构中创建一个 php class。
class EzCredentialStruct
{
public $AgentCode;
public $HashedPassword;
public $Username;
public function getAgentCode() : string
{
return $this->AgentCode;
}
public function setAgentCode(string $AgentCode) : EzCredentialStruct
{
$this->AgentCode = $AgentCode;
return $this;
}
public function getHashedPassword() : string
{
return $this->HashedPassword;
}
public function setHashedPassword(string $HashedPassword) : EzCredentialStruct
{
$this->HashedPassword = $HashedPassword;
return $this;
}
public function getUsername() : string
{
return $this->Username;
}
public function setUsername(string $Username) : EzCredentialStruct
{
$this->Username = $Username;
return $this;
}
}
class GetLocalRatesStruct
{
public $credentials;
public $payincurcode;
public $transferType;
public function getCredentials() : EzCredentialStruct
{
return $this->credentials;
}
public function setCredentials(EzCredentialStruct $credentials) : GetLocalRatesStruct
{
$this->credentials = $credentials;
return $this;
}
public function getPayincurcode() : string
{
return $this->payincurcode;
}
public function setPayincurcode(string $payincurcode) : GetLocalRatesStruct
{
$this->payincurcode = $payincurcode;
return $this;
}
public function getTransferType() : string
{
return $this->transferType;
}
public function setTransferType(string $transferType) : GetLocalRatesStruct
{
$this->transferType = $transferType;
return $this;
}
}
这两个 class 是类型数组中所有结构的示例。因此,将所有结构记为 classes。稍后您会发现好处。
2。 Soap 客户端和 classmap 选项
现在,由于我们已将 web 服务的使用类型声明为 classes,我们可以启动 soap 客户端。使用正确的选项启动 soap 客户端很重要。始终将客户端包装在 try/catch 块中。
try {
$options = [
'cache_wsdl' => WSDL_CACHE_NONE,
'soap_version' => SOAP_1_2,
'trace' => true,
'classmap' => [
'EzCredential' => 'EzCredentialStruct',
'GetLocalRates' => 'GetLocalRatesStruct',
],
];
$wsdl = 'path/to/the/webservice/endpoint?wsdl';
$client = new \SoapClient(
$wsdl,
$options,
);
} catch (\SoapFault $e) {
// error handling
}
如您所见,选项数组中有一个 classmap
键。 classmap 将类型路由到特定的 php classes。在此示例中,我们仅使用我们之前定义的两个示例类型 classes。 soap 客户端现在可以自动创建网络服务需要的 xml 字符串。
3。全部放在一起
现在,我们已经拥有了正确的 soap 请求所需的一切,我们的代码应该如下所示。
$credentials = (new EzCredentialStruct())
->setAgentCode($agentCode)
->setHashedPassword($hashedPassword)
->setUsername($username);
$request = (new GetLocalRatesStruct())
->setCredentials($credentials)
->setPayincurcode($code)
->setTransferType($transferType);
$result = $client->GetLocalRates($request);
结论
乍一看,这种方法可能看起来工作量更大。但是,对于 web 服务声明的每个结构,将类型与函数和编程 classes 分开是有意义的。 webservice函数的调用会更简单,因为我们只传递一个对象作为参数。不再有奇怪的数组构造。一切都在原位,可以重复使用。
PS:此代码未经测试。不建议在生产中使用它。此代码仅用作示例。它可能包含错误。
原问题:
我正在尝试从 SOAP API wsdl
link 获取数据。我的代码如下。但是我得到了这个错误。任何人都可以帮忙吗?
Error Message: OperationFormatter encountered an invalid Message body. Expected to find node type 'Element' with name 'GetLocalRates' and namespace 'http://tempuri.org/'. Found node type 'Element' with name 'soapenv:Envelope' and namespace 'http://www.w3.org/2003/05/soap-envelope'
<?php
$api_link = 'https://www.my-api-link.com/RateAPI.svc/SSL?wsdl';
//setting xml request to api
$request = '<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope" xmlns:tem="http://tempuri.org/" xmlns:ezr="http://schemas.datacontract.org/2004/07/EzremitAPI.Entities">
<soapenv:Body>
<tem:GetLocalRates>
<tem:credentials>
<ezr:AgentCode>####</ezr:AgentCode>
<ezr:HashedPassword>####</ezr:HashedPassword>
<ezr:Username>####</ezr:Username>
</tem:credentials>
<tem:payincurcode>####</tem:payincurcode>
<tem:transferType>####</tem:transferType>
</tem:GetLocalRates>
</soapenv:Body>
</soapenv:Envelope>';
try {
$client = new SoapClient($api_link, array('cache_wsdl' => WSDL_CACHE_NONE, 'soap_version' => SOAP_1_2, 'reliable' => 1.2 , 'useWSA' => TRUE ) );
$soapaction = "http://tempuri.org/IRateAPI/GetLocalRates";
$client->soap_defencoding = 'UTF-8';
// Apply WSA headers
$headers = array();
$headers[] = new SoapHeader('http://www.w3.org/2005/08/addressing', 'To', 'https://www.my-api-link.com/RateAPI.svc/SSL?wsdl', true);
$headers[] = new SoapHeader('http://www.w3.org/2005/08/addressing', 'Action', 'http://tempuri.org/IRateAPI/GetLocalRates', true);
$client->__setSoapHeaders($headers);
$response = $client->GetLocalRates(new SoapVar($request, XSD_ANYXML));
print_r($response);
}
catch(Exception $e) {
echo $e->getMessage();
}
?>
编辑 1(根据第一条评论修改代码)
结果:
http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher/faults:Receivera:InternalServiceFaultOperationFormatter encountered an invalid Message body. Expected to find node type 'Element' with name 'GetLocalRates' and namespace 'http://tempuri.org/'. Found node type 'Element' with name 'soapenv:Envelope' and namespace 'http://www.w3.org/2003/05/soap-envelope'OperationFormatter encountered an invalid Message body. Expected to find node type 'Element' with name 'GetLocalRates' and namespace 'http://tempuri.org/'. Found node type 'Element' with name 'soapenv:Envelope' and namespace 'http://www.w3.org/2003/05/soap-envelope' at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeBody(XmlDictionaryReader reader, MessageVersion version, String action, MessageDescription messageDescription, Object[] parameters, Boolean isRequest)
at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeBodyContents(Message message, Object[] parameters, Boolean isRequest)
at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeRequest(Message message, Object[] parameters)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.DeserializeInputs(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)System.Runtime.Serialization.SerializationException
编辑 2:
$client->__getTypes() 结果:
array(8) {
[0]=>
string(84) "struct EzCredential {
string AgentCode;
string HashedPassword;
string Username;
}"
[1]=>
string(58) "struct ArrayOfCurrencyRate {
CurrencyRate CurrencyRate;
}"
[2]=>
string(187) "struct CurrencyRate {
decimal AgentMargin;
string CurrencyCode;
string CurrencyDescription;
decimal FromAmount;
decimal Rate;
string RateType;
decimal ToAmount;
string Trantype;
}"
[3]=>
string(95) "struct GetLocalRates {
EzCredential credentials;
string payincurcode;
string transferType;
}"
[4]=>
string(74) "struct GetLocalRatesResponse {
ArrayOfCurrencyRate GetLocalRatesResult;
}"
[5]=>
string(8) "int char"
[6]=>
string(17) "duration duration"
[7]=>
string(11) "string guid"
}
$client->__getFunctions() 结果:
array(1) {
[0]=>
string(62) "GetLocalRatesResponse GetLocalRates(GetLocalRates $parameters)"
}
固定:下面使用而不是 XML 信封。非常感谢,@Marcel。你是一个伟大的救世主。
$requestParams = array( 'credentials' => array('AgentCode' => $acode,
'HashedPassword' => $hpass,
'Username' => $uname),
'payincurcode' => $ccode,
'transferType' => $ttype
);
$response = $client->GetLocalRates( $requestParams );
预先感谢您用缺失的数据更新了您的问题。此示例是本机 php 示例,展示了如何以面向对象的方式使用 soap 函数和类型。
1.类型和 classes
如您在类型数组中所见,有几种类型声明为 struct。如果你喜欢这么说,structs 可以被称为 PHP classes。因此,让我们从每个结构中创建一个 php class。
class EzCredentialStruct
{
public $AgentCode;
public $HashedPassword;
public $Username;
public function getAgentCode() : string
{
return $this->AgentCode;
}
public function setAgentCode(string $AgentCode) : EzCredentialStruct
{
$this->AgentCode = $AgentCode;
return $this;
}
public function getHashedPassword() : string
{
return $this->HashedPassword;
}
public function setHashedPassword(string $HashedPassword) : EzCredentialStruct
{
$this->HashedPassword = $HashedPassword;
return $this;
}
public function getUsername() : string
{
return $this->Username;
}
public function setUsername(string $Username) : EzCredentialStruct
{
$this->Username = $Username;
return $this;
}
}
class GetLocalRatesStruct
{
public $credentials;
public $payincurcode;
public $transferType;
public function getCredentials() : EzCredentialStruct
{
return $this->credentials;
}
public function setCredentials(EzCredentialStruct $credentials) : GetLocalRatesStruct
{
$this->credentials = $credentials;
return $this;
}
public function getPayincurcode() : string
{
return $this->payincurcode;
}
public function setPayincurcode(string $payincurcode) : GetLocalRatesStruct
{
$this->payincurcode = $payincurcode;
return $this;
}
public function getTransferType() : string
{
return $this->transferType;
}
public function setTransferType(string $transferType) : GetLocalRatesStruct
{
$this->transferType = $transferType;
return $this;
}
}
这两个 class 是类型数组中所有结构的示例。因此,将所有结构记为 classes。稍后您会发现好处。
2。 Soap 客户端和 classmap 选项
现在,由于我们已将 web 服务的使用类型声明为 classes,我们可以启动 soap 客户端。使用正确的选项启动 soap 客户端很重要。始终将客户端包装在 try/catch 块中。
try {
$options = [
'cache_wsdl' => WSDL_CACHE_NONE,
'soap_version' => SOAP_1_2,
'trace' => true,
'classmap' => [
'EzCredential' => 'EzCredentialStruct',
'GetLocalRates' => 'GetLocalRatesStruct',
],
];
$wsdl = 'path/to/the/webservice/endpoint?wsdl';
$client = new \SoapClient(
$wsdl,
$options,
);
} catch (\SoapFault $e) {
// error handling
}
如您所见,选项数组中有一个 classmap
键。 classmap 将类型路由到特定的 php classes。在此示例中,我们仅使用我们之前定义的两个示例类型 classes。 soap 客户端现在可以自动创建网络服务需要的 xml 字符串。
3。全部放在一起
现在,我们已经拥有了正确的 soap 请求所需的一切,我们的代码应该如下所示。
$credentials = (new EzCredentialStruct())
->setAgentCode($agentCode)
->setHashedPassword($hashedPassword)
->setUsername($username);
$request = (new GetLocalRatesStruct())
->setCredentials($credentials)
->setPayincurcode($code)
->setTransferType($transferType);
$result = $client->GetLocalRates($request);
结论
乍一看,这种方法可能看起来工作量更大。但是,对于 web 服务声明的每个结构,将类型与函数和编程 classes 分开是有意义的。 webservice函数的调用会更简单,因为我们只传递一个对象作为参数。不再有奇怪的数组构造。一切都在原位,可以重复使用。
PS:此代码未经测试。不建议在生产中使用它。此代码仅用作示例。它可能包含错误。