alexejk / go-xmlrpc

An XML-RPC Client for Go
https://alexejk.io/article/handling-xmlrpc-in-go/
MIT License
19 stars 7 forks source link

Error: reading body number of exported fields (3) on response type doesnt match expectation (1) #75

Closed graugans closed 10 months ago

graugans commented 11 months ago

First of all thank you very much for your great work. I have an weird XML-RPC server implementation which does response with a different amount of values in case of an internal error occurred, hence the message

Error: reading body number of exported fields (3) on response type doesn't match expectation (1)

Is there an option to omit the struct value in case it is empty? Maybe something like this:

result := &struct {
     Error   int                                                                                                                                                            
     Version string `xmlrpc:"Version,omitempty"`                                                                                                                              
     Type    string `xmlrpc:"Type,omitempty"`
}{}

In case of a successful operation the returned data from the device is {0,"4711","AA"} and in case of an error only {-100} is returned.

alexejk commented 10 months ago

Hi @graugans, I haven't seen a case like this but i think it should be possible to implement. I'll take a look at this

graugans commented 10 months ago

This little guy behaves like this: https://www.ifm.com/us/en/product/O3D200 In case you have an idea or example, how this could be handled I can try it by myself.

alexejk commented 10 months ago

Do you have an example of how the payload looks like on the wire? XML-RPC should only have one parameter in the response.

An XML-RPC response can only contain one parameter, despite the use of the enclosing params element. That parameter, may, of course, be an array or a struct, so it is possible to return multiple values.

However what im trying to understand - does it respond with a struct + flexible amount of fields OR does it respond with multiple parameters directly?

graugans commented 10 months ago

I will try to grab some communication examples tomorrow I guess this will provide more details.

alexejk commented 10 months ago

I've given it a quick stab based on the scarce information i have right now. You can give 0.5.0-RC1 a try when you have a moment, i will make a proper release if this works or after some fixes.

graugans commented 10 months ago

Sorry for the delay, this was a busy day. A successful call captured in Wireshark looks like this:

POST /RPC1/ HTTP/1.1
Host: 10.121.123.14:8080
Accept-Encoding: gzip
Content-Type: text/xml
User-Agent: Python-xmlrpc/3.12
Content-Length: 109

<?xml version='1.0'?>
<methodCall>
<methodName>MDAXMLConnectCP</methodName>
<params>
</params>
</methodCall>
HTTP/1.1 200 OK
Server: XMLRPC++ 0.7
Content-Type: text/xml
Content-length: 222

<?xml version="1.0" encoding="UTF-8"?>
<methodResponse><params><param>
    <value><array><data><value><i4>0</i4></value><value>4099</value><value>O3D217AC</value></data></array></value>
</param></params></methodResponse>

When the device returns an error it looks like this:

POST /RPC1/ HTTP/1.1
Host: 10.121.123.14:8080
Accept-Encoding: gzip
Content-Type: text/xml
User-Agent: Python-xmlrpc/3.12
Content-Length: 109

<?xml version='1.0'?>
<methodCall>
<methodName>MDAXMLConnectCP</methodName>
<params>
</params>
</methodCall>
HTTP/1.1 200 OK
Server: XMLRPC++ 0.7
Content-Type: text/xml
Content-length: 183

<?xml version="1.0" encoding="UTF-8"?>
<methodResponse><params><param>
    <value><array><data><value><i4>-102</i4></value></data></array></value>
</param></params></methodResponse>

I have captured this with a small Python example.

I hopefully find some time on Thursday to give your branch a try

alexejk commented 10 months ago

Okay so your response contains a single array value, so that should just work by specifying that your response is expected to be an array. This would not require any modifications.

type MyResponse struct {
    Response []any
}

However I've noticed that your system does NOT wrap all values in data types. First parameter is correctly wrapped into which identifies the integer. Problem is that second and third parameter in the array should be wrapped into or but they aren't.

Today the library will return nil on them... I will look into changing implementation to have the flag that would default unknown types to string (or attempt at least) but that will likely come at certain tradeoffs.. Will get back to you on this.

graugans commented 10 months ago

Yeah, the device is not behaving in an optimal way. Anyway according to the specification here: http://xmlrpc.com/spec.md the default is string

If no type is indicated, the type is string.

alexejk commented 10 months ago

Thats a good catch! then I don't need an extra flag for that. I will adjust the behavior, as this is clearly then a bug on my side.

alexejk commented 10 months ago

This should be resolved in v0.4.1. You can check the test-case i added with your data sample (slightly modified) for example how to do the data mapping for this type of responses.

graugans commented 10 months ago

Sorry for the late feedback, with https://github.com/alexejk/go-xmlrpc/releases/tag/v0.4.1 and the any type Array it works like a charm. Thanks for your support and great library.