Add SOAP action support.

This commit is contained in:
John Beisley 2013-09-29 11:23:10 +01:00
parent 489c180de9
commit 1b8d66b887
3 changed files with 119 additions and 3 deletions

View File

@ -19,7 +19,16 @@ func (i indentLevel) String() string {
func displayDevice(indent indentLevel, device *goupnp.Device) {
fmt.Println(indent.String(), device)
for _, srv := range device.Services {
fmt.Println((indent + 1).String(), srv)
fmt.Println((indent + 1).String(), srv, srv.SCPDURL.URL.String(), srv.ControlURL.URL.String())
fmt.Println(goupnp.ServiceTypeWANPPPConnection, srv.ServiceType)
if srv.ServiceType == goupnp.ServiceTypeWANPPPConnection {
results, err := goupnp.PerformSoapAction(goupnp.ServiceTypeWANPPPConnection, "GetExternalIPAddress", &srv.ControlURL.URL, nil)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(results)
}
}
}
for _, subdev := range device.Devices {
displayDevice(indent+1, subdev)

View File

@ -11,11 +11,11 @@ import (
// Non-exhaustive set of UPnP service types.
const (
ServiceTypeLayer3Forwarding = "urn:schemas-upnp-org:service:Layer3Forwarding:1"
ServiceTypeWANCommonInterfaceConfig = "urn:schemas-upnp-org:WANCommonInterfaceConfig:1"
ServiceTypeWANCommonInterfaceConfig = "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1"
// WANPPPConnection is typically useful with regard to the external IP and
// port forwarding.
// http://upnp.org/specs/gw/UPnP-gw-WANPPPConnection-v1-Service.pdf
ServiceTypeWANPPPConnection = "urn:schemas-upnp-org:WANPPPConnection:1"
ServiceTypeWANPPPConnection = "urn:schemas-upnp-org:service:WANPPPConnection:1"
)
// Non-exhaustive set of UPnP device types.

107
soap.go Normal file
View File

@ -0,0 +1,107 @@
// 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)
fmt.Println(response.Header)
var responseEnv SoapEnvelope
if err := decoder.Decode(&responseEnv); err != nil {
return nil, err
}
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 {
Action SoapAction `xml:",any"`
}
type SoapAction struct {
XMLName xml.Name
Arguments []SoapArgument `xml:",any"`
}
type SoapArgument struct {
XMLName xml.Name
Value string `xml:",chardata"`
}