Correct the encoding of SOAP action arguments.
Also adds a test for this, and the decoding of the response arguments.
This commit is contained in:
parent
5c55e50548
commit
788bb66b80
40
soap/soap.go
40
soap/soap.go
@ -9,11 +9,12 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
soapEncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/"
|
soapEncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/"
|
||||||
soapPrefix = `<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>`
|
soapPrefix = xml.Header + `<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>`
|
||||||
soapSuffix = `</s:Body></s:Envelope>`
|
soapSuffix = `</s:Body></s:Envelope>`
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,6 +30,8 @@ func NewSOAPClient(endpointURL url.URL) *SOAPClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PerformSOAPAction makes a SOAP request, with the given action.
|
// PerformSOAPAction makes a SOAP request, with the given action.
|
||||||
|
// inAction and outAction must both be pointers to structs with string fields
|
||||||
|
// only.
|
||||||
func (client *SOAPClient) PerformAction(actionNamespace, actionName string, inAction interface{}, outAction interface{}) error {
|
func (client *SOAPClient) PerformAction(actionNamespace, actionName string, inAction interface{}, outAction interface{}) error {
|
||||||
requestBytes, err := encodeRequestAction(actionNamespace, actionName, inAction)
|
requestBytes, err := encodeRequestAction(actionNamespace, actionName, inAction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -47,7 +50,7 @@ func (client *SOAPClient) PerformAction(actionNamespace, actionName string, inAc
|
|||||||
ContentLength: int64(len(requestBytes)),
|
ContentLength: int64(len(requestBytes)),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("goupnp: error performing SOAP HTTP request: %v", err)
|
||||||
}
|
}
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
if response.StatusCode != 200 {
|
if response.StatusCode != 200 {
|
||||||
@ -57,7 +60,7 @@ func (client *SOAPClient) PerformAction(actionNamespace, actionName string, inAc
|
|||||||
responseEnv := newSOAPEnvelope()
|
responseEnv := newSOAPEnvelope()
|
||||||
decoder := xml.NewDecoder(response.Body)
|
decoder := xml.NewDecoder(response.Body)
|
||||||
if err := decoder.Decode(responseEnv); err != nil {
|
if err := decoder.Decode(responseEnv); err != nil {
|
||||||
return err
|
return fmt.Errorf("goupnp: error decoding response body: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if responseEnv.Body.Fault != nil {
|
if responseEnv.Body.Fault != nil {
|
||||||
@ -66,7 +69,7 @@ func (client *SOAPClient) PerformAction(actionNamespace, actionName string, inAc
|
|||||||
|
|
||||||
if outAction != nil {
|
if outAction != nil {
|
||||||
if err := xml.Unmarshal(responseEnv.Body.RawAction, outAction); err != nil {
|
if err := xml.Unmarshal(responseEnv.Body.RawAction, outAction); err != nil {
|
||||||
return err
|
return fmt.Errorf("goupnp: error unmarshalling out action: %v, %v", err, responseEnv.Body.RawAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,8 +97,7 @@ func encodeRequestAction(actionNamespace, actionName string, inAction interface{
|
|||||||
xml.EscapeText(requestBuf, []byte(actionNamespace))
|
xml.EscapeText(requestBuf, []byte(actionNamespace))
|
||||||
requestBuf.WriteString(`">`)
|
requestBuf.WriteString(`">`)
|
||||||
if inAction != nil {
|
if inAction != nil {
|
||||||
requestEnc := xml.NewEncoder(requestBuf)
|
if err := encodeRequestArgs(requestBuf, inAction); err != nil {
|
||||||
if err := requestEnc.Encode(inAction); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,6 +108,32 @@ func encodeRequestAction(actionNamespace, actionName string, inAction interface{
|
|||||||
return requestBuf.Bytes(), nil
|
return requestBuf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func encodeRequestArgs(w *bytes.Buffer, inAction interface{}) error {
|
||||||
|
in := reflect.Indirect(reflect.ValueOf(inAction))
|
||||||
|
if in.Kind() != reflect.Struct {
|
||||||
|
return fmt.Errorf("goupnp: SOAP inAction is not a struct but of type %v", in.Type())
|
||||||
|
}
|
||||||
|
enc := xml.NewEncoder(w)
|
||||||
|
nFields := in.NumField()
|
||||||
|
inType := in.Type()
|
||||||
|
for i := 0; i < nFields; i++ {
|
||||||
|
field := inType.Field(i)
|
||||||
|
argName := field.Name
|
||||||
|
if nameOverride := field.Tag.Get("soap"); nameOverride != "" {
|
||||||
|
argName = nameOverride
|
||||||
|
}
|
||||||
|
value := in.Field(i)
|
||||||
|
if value.Kind() != reflect.String {
|
||||||
|
return fmt.Errorf("goupnp: SOAP arg %q is not of type string, but of type %v", argName, value.Type())
|
||||||
|
}
|
||||||
|
if err := enc.EncodeElement(value.Interface(), xml.StartElement{xml.Name{"", argName}, nil}); err != nil {
|
||||||
|
return fmt.Errorf("goupnp: error encoding SOAP arg %q: %v", argName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enc.Flush()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type soapEnvelope struct {
|
type soapEnvelope struct {
|
||||||
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
|
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
|
||||||
EncodingStyle string `xml:"http://schemas.xmlsoap.org/soap/envelope/ encodingStyle,attr"`
|
EncodingStyle string `xml:"http://schemas.xmlsoap.org/soap/envelope/ encodingStyle,attr"`
|
||||||
|
85
soap/soap_test.go
Normal file
85
soap/soap_test.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package soap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type capturingRoundTripper struct {
|
||||||
|
err error
|
||||||
|
resp *http.Response
|
||||||
|
capturedReq *http.Request
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *capturingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
rt.capturedReq = req
|
||||||
|
return rt.resp, rt.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionInputs(t *testing.T) {
|
||||||
|
url, err := url.Parse("http://example.com/soap")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rt := &capturingRoundTripper{
|
||||||
|
err: nil,
|
||||||
|
resp: &http.Response{
|
||||||
|
StatusCode: 200,
|
||||||
|
Body: ioutil.NopCloser(bytes.NewBufferString(`
|
||||||
|
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
|
||||||
|
<s:Body>
|
||||||
|
<u:myactionResponse xmlns:u="mynamespace">
|
||||||
|
<A>valueA</A>
|
||||||
|
<B>valueB</B>
|
||||||
|
</u:myactionResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>
|
||||||
|
`)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client := SOAPClient{
|
||||||
|
EndpointURL: *url,
|
||||||
|
HTTPClient: http.Client{
|
||||||
|
Transport: rt,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type In struct {
|
||||||
|
Foo string
|
||||||
|
Bar string `soap:"bar"`
|
||||||
|
}
|
||||||
|
type Out struct {
|
||||||
|
A string
|
||||||
|
B string
|
||||||
|
}
|
||||||
|
in := In{"foo", "bar"}
|
||||||
|
gotOut := Out{}
|
||||||
|
err = client.PerformAction("mynamespace", "myaction", &in, &gotOut)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantBody := (soapPrefix +
|
||||||
|
`<u:myaction xmlns:u="mynamespace">` +
|
||||||
|
`<Foo>foo</Foo>` +
|
||||||
|
`<bar>bar</bar>` +
|
||||||
|
`</u:myaction>` +
|
||||||
|
soapSuffix)
|
||||||
|
body, err := ioutil.ReadAll(rt.capturedReq.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
gotBody := string(body)
|
||||||
|
if wantBody != gotBody {
|
||||||
|
t.Errorf("Bad request body\nwant: %q\n got: %q", wantBody, gotBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantOut := Out{"valueA", "valueB"}
|
||||||
|
if !reflect.DeepEqual(wantOut, gotOut) {
|
||||||
|
t.Errorf("Bad output\nwant: %+v\n got: %+v", wantOut, gotOut)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user