Compatibility between PHP SOAP and Python SOAP libraries - php

The Moodle LMS can be used to quiz students. One optional question type (Opaque) uses SOAP to request questions from a service ( code at https://goo.gl/iGDIiy ).
There is a working "question server" implemented in PHP using the WSDL defined at https://goo.gl/kESENq
The complication that I am running into is that the code in the question type expected some return values to be a string. The response from the functioning test server looks like...
<SOAP-ENV:Body>
<ns1:getEngineInfoResponse>
<getEngineInfoReturn xsi:type="SOAP-ENC:string">
<engineinfo>
Note the xsi:type=..string. The data is actually returned as an XML formatted string by the PHP application:
/**
* A dummy implementation of the getEngineInfo method.
* #return string of XML.
*/
public function getEngineInfo() {
return '<engineinfo>
I'm trying to interface this to a Python+SOAP server. I've tried using both ZSI and Spyne. With ZSI, I used the WSDL provided with the sample server to generate the code stubs. The corresponding code simply returns a string much like the PHP code. The response from that is:
<ns1:getEngineInfoResponse>
<getEngineInfoReturn>
<engineinfo>
Note the lack of the string type modifier. This response gets received by the PHP code in Moodle as an object containing a single field with no name but containing the returned XML string. Because it's an object containing a string, rather than just a string, the code fails to parse the result correctly.
With Spyne, I get:
soap11env:Body>
<tns:getEngineInfoResponse>
<tns:getEngineInfoReturn><engineinfo>
from this code:
#srpc(_returns=String, _body_style='wrapped',
_out_variable_name="getEngineInfoReturn"
)
def getEngineInfo():
resp = "<engineinfo>\n"
This also returns an object with a single element (now named getEngineInfoReturn) that contains the appropriate string.
However, the receiver (which was happy with the WSDL produced)
still expects a string and not an object containing a string.
I'd rather get the Spyne version working than the ZSI version because it's easier to use. I've used SOAP before, but am not certain if the PHP code is making an unwarranted assumption about the return format or if I should be able to torque Spyne into producing that format. From reading the source code for the decorator function, I don't think I can.
So, is this a poorly coded client or is the expected SOAP schema normal? How can I get Spyne (or ZIS) to produce something similar?

Pass _out_body_style='bare' to #rpc.

Related

Azure Function App - PHP & XML Output

In a very similar issue described in this post https://github.com/Azure/azure-webjobs-sdk-script/issues/942 - When pushing an output with XML content, Azure seems to wrap the content in serialized tags. Effectively nesting the XML in its own.
There appears to be a way to set the headers and provide additional context to the output in NodeJS. You can define the value as XML which in turn ensures that the content isn't wrapped.
Does anyone have a similar solution for PHP? I've tried defining Headers/Content Type with no success.
Thanks in advance!
T
As Mamaso said, this is a known issue: php azure function can't set content type #1554.
This is a known issue - the isRaw flag only applies to node at present. It can only be set in script languages if you serialize to a json string, which turns any bytes into base64 strings (defeating the purpose of isRaw). I'll have a think on how to indicate a raw response from script languages.
Unfortunately there is not a workaround, unless if you can use C#, F#, or node.

PHP string parameter best practice

I have a PHP function that internally builds an object graph using among other things one string parameter, then uses json_encode() to create a JSON string and then post the JSON string to a remote web service.
Like this:
function send($text)
{
$payload = array(
'text' => $text
// Set additional properties here ...
);
$payload_json = json_encode($payload);
// Post $payload_json to remote service with Curl ...
}
From the manual of json_encode (http://php.net/manual/en/function.json-encode.php)
All string data must be UTF-8 encoded.
I see a couple of options:
Attempt to validate that $text is in fact UTF-8 and throw an exception if it is not
Attempt to detect the encoding in $text and convert it to UTF-8 if necessary
Return false when $text is not UTF-8
Communicate with my API users in documentation that $text must be UTF-8
Check for error with json_last_error() and throw an exception if an error was encountered
What is the best practice?
You should always communicate to the users of the API what you're expecting.
If you expect UTF-8 encoded text, say so in your documentation.
Once it's in there, you should return a descriptive error such as "Invalid Encoding, for more information read the documentation: link" where link goes to the relevant page for the call that failed
This way, you're not responsible anymore, and the developers that use your API will know what is going wrong, and you don't have to worry about it in your API.
You're the developer, it's your API, and your API has it's own rules, if people want to use it, they need to follow the rules you set.

simplexml_load_string not parsing my XML string. Charset issue?

I'm using the following PHP code to read XML data from NOAA's tide reporting station API:
$rawxml = file_get_contents(
"http://opendap.co-ops.nos.noaa.gov/axis/webservices/activestations/"
."response.jsp?v=2&format=xml&Submit=Submit"
);
$rawxml = utf8_encode($rawxml);
$ob = simplexml_load_string($rawxml);
var_dump($ob);
Unfortunately, I end up with it displaying this:
object(SimpleXMLElement)#246 (0) { }
It looks to me like the XML is perfectly well-formed - why won't this parse? From looking at another question (Simplexml_load_string() fail to parse error) I got the idea that the header might be the problem - the http call does indeed return a charset value of "ISO-8859-1". But adding in the utf8_encode() call doesn't seem to do the trick.
What's especially confusing is that simplexml_load_string() doesn't actually fail - it returns a cheerful XML array, just with nothing in it!
You've been fooled (and had me fooled) by the oldest trick in the SimpleXML book: SimpleXML doesn't parse the whole document into a PHP object, it presents a PHP API to an internal structure. Functions like var_dump can't see this structure, so don't always give a useful idea of what's in the object.
The reason it looks "empty" is that it is listing the children of the root element which are in the default namespace - but there aren't any, they're all in the "soapenv:" namespace.
To access namespaced elements, you need to use the children() method, passing in the full namespace name (recommended) or its local prefix (simpler, but could be broken by changes in the way the file is generated the other end). To switch back to the "default namespace", use ->children(null).
So you could get the ID attribute of the first stationV2 element like this (live demo):
// Define constant for the namespace names, rather than relying on the prefix the remote service uses remaining stable
define('NS_SOAP', 'http://schemas.xmlsoap.org/soap/envelope/');
// Download the XML
$rawxml = file_get_contents("http://opendap.co-ops.nos.noaa.gov/axis/webservices/activestations/response.jsp?v=2&format=xml&Submit=Submit");
// Parse it
$ob = simplexml_load_string($rawxml);
// Use it!
echo $ob->children(NS_SOAP)->Body->children(null)->ActiveStationsV2->stationsV2->stationV2[0]['ID'];
I've written some debugging functions to use with SimpleXML which should be much less misleading than var_dump etc. Here's a live demo with your code and simplexml_dump.

Remove double-quotes from a json_encoded string on the keys

I have a json_encoded array which is fine.
I need to strip the double-quotes on all of the keys of the json string on returning it from a function call.
How would I go about doing this and returning it successfully?
Thanks!
I do apologise, here is a snippet of the json code:
{"start_date":"2011-01-01 09:00","end_date":"2011-01-01 10:00","text":"test"}
Just to add a little more info:
I will be retrieving the JSON via an AJAX request, so if it would be easier, I am open to ideas in how to do this on the javascript side.
EDITED as per anubhava's comment
$str = '{"start_date":"2011-01-01 09:00","end_date":"2011-01-01 10:00","text":"test"}';
$str = preg_replace('/"([^"]+)"\s*:\s*/', '$1:', $str);
echo $str;
This certainly works for the above string, although there maybe some edge cases that I haven't thought of for which this will not work. Whether this will suit your purposes depends on how static the format of the string and the elements/values it contains will be.
TL;DR: Missing quotes is how Chrome shows it is a JSON object instead of a string. Ensure that you have Header('Content-Type: application/json; charset=UTF8'); in PHP's AJAX response to solve the real problem.
DETAILS:
A common reason for wanting to solve this problem is due to finding this difference while debugging the processing of returned AJAX data.
In my case I saw the difference using Chrome's debugging tools. When connected to the legacy system, upon success, Chrome showed that there were no quotes shown around keys in the response according to the debugger. This allowed the object to be immediately treated as an object without using a JSON.parse() call. Debugging my new AJAX destination, there were quotes shown in the response and variable was a string and not an object.
I finally realized the true issue when I tested the AJAX response externally saw the legacy system actually DID have quotes around the keys. This was not what the Chrome dev tools showed.
The only difference was that on the legacy system there was a header specifying the content type. I added this to the new (WordPress) system and the calls were now fully compatible with the original script and the success function could handle the response as an object without any parsing required. Now I can switch between the legacy and new system without any changes except the destination URL.

Problems using PHP SoapClient to pass an encrypted value to a .Net SOAP service

I have a SOAP service I am calling with PHP 5.3.1's builtin SoapClient. The first operation I must perform on the service is a custom authentication operation, and one of the required parameters I must pass is a 3DES encrypted string which I am creating using PHP's mcrypt, like so:
$encryptionKey = '1234myKey1234';
$currentFormattedDate = date ("Y/m/d H:i");
$encryptedString = mcrypt_encrypt('tripledes', $encryptionKey, $currentFormattedDate, 'ecb');
If I try to just pass $encryptedString as I get it from mcrypt_encrypt() I get a fatal error on my side and no call is made:
Fatal error: SOAP-ERROR: Encoding: string 'd\xe0...' is not a valid utf-8 string in /path/to/file
However if I utf8_encode() the string as such:
$encryptedString = utf8_encode($encryptedString)
Then the call is made but their webservice responds with the following error:
The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://tempuri.org/:argStatusDate. The InnerException message was 'There was an error deserializing the object of type System.String. The byte 0x19 is not valid at this location. Line 2, position 318.'.
This is the closest I can get to success with this process after having tried so many things that I'm back to square one. I have verified I can just pass a bogus string which results in the expected response of not being able to authenticate.
I don't think this should make any difference since I believe the SOAP call is ultimately made as utf8, but I have tried setting 'encoding' => 'ISO-8859-1' when constructing my SoapClient in PHP and I get the same error. The call is made but the server responds with the deserialization error.
Does anyone know a better way for me to treat this encrypted string that will please both my PHP client and their .Net webservice?
Maybe the problem is on their end?
FWIW, I can also request that we change the encryption method to "Rijndael AES Block Cypher" per their documentation. Not sure if that would result in an easier to handle string.
You probably need to encode the data in a base 64 encoded CDATA segment inside the opening and closing tags. You might want to ask the creater of the service for a sample, or - if it is a webservice - try to download the definition or even create a client through discovery. Note that the last link was found using Google search, I've been out of PHP for a while.
[EDIT] changing the cipher won't help for this, although anything is better than ECB encoding XML

Categories