goupnp/soap.go
2013-10-03 20:38:23 +01:00

122 lines
3.0 KiB
Go

// Definition for the SOAP structure required for UPnP's SOAP usage.
package goupnp
import (
"bytes"
"encoding/xml"
"fmt"
"io/ioutil"
"net/http"
"net/url"
)
const (
SoapEncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/"
)
type NameValue struct {
Name string
Value string
}
// NewSoapAction creates a SoapEnvelope with the given action and arguments.
func newSoapAction(actionNamespace, actionName string, arguments []NameValue) *SoapEnvelope {
env := &SoapEnvelope{
EncodingStyle: SoapEncodingStyle,
Body: SoapBody{
Action: SoapAction{
XMLName: xml.Name{actionNamespace, actionName},
Arguments: make([]SoapArgument, len(arguments)),
},
},
}
for i := range arguments {
env.Body.Action.Arguments[i].XMLName.Local = arguments[i].Name
env.Body.Action.Arguments[i].Value = arguments[i].Value
}
return env
}
// PerformSoapAction makes a SOAP request, with the given action.
func PerformSoapAction(actionNamespace, actionName string, url *url.URL, arguments []NameValue) ([]NameValue, error) {
requestEnv := newSoapAction(actionNamespace, actionName, arguments)
requestBytes, err := xml.Marshal(requestEnv)
if err != nil {
return nil, err
}
client := http.Client{}
response, err := client.Do(&http.Request{
Method: "POST",
URL: url,
Header: http.Header{
"SOAPACTION": []string{actionNamespace + "#" + actionName},
"CONTENT-TYPE": []string{"text/xml; charset=\"utf-8\""},
},
Body: ioutil.NopCloser(bytes.NewBuffer(requestBytes)),
// Set ContentLength to avoid chunked encoding - some servers might not support it.
ContentLength: int64(len(requestBytes)),
})
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode != 200 {
return nil, fmt.Errorf("goupnp: SOAP request got %s response from %s", response.Status, url.String())
}
decoder := xml.NewDecoder(response.Body)
var responseEnv SoapEnvelope
if err := decoder.Decode(&responseEnv); err != nil {
return nil, err
}
if responseEnv.Body.Fault != nil {
return nil, responseEnv.Body.Fault
}
results := make([]NameValue, len(responseEnv.Body.Action.Arguments))
for i, soapArg := range responseEnv.Body.Action.Arguments {
results[i] = NameValue{
Name: soapArg.XMLName.Local,
Value: soapArg.Value,
}
}
return results, nil
}
type SoapEnvelope struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
EncodingStyle string `xml:"http://schemas.xmlsoap.org/soap/envelope/ encodingStyle,attr"`
Body SoapBody `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
}
type SoapBody struct {
Fault *SoapFault `xml:"Fault"`
Action SoapAction `xml:",any"`
}
type SoapFault struct {
FaultCode string `xml:"faultcode"`
FaultString string `xml:"faultstring"`
Detail string `xml:"detail"`
}
func (err *SoapFault) Error() string {
return fmt.Sprintf("SOAP fault: %s", err.FaultString)
}
type SoapAction struct {
XMLName xml.Name
Arguments []SoapArgument `xml:",any"`
}
type SoapArgument struct {
XMLName xml.Name
Value string `xml:",chardata"`
}