Tidy up the SOAP components a bit.
This commit is contained in:
parent
80bd2aa934
commit
0b82043f96
@ -3,6 +3,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/huin/goupnp"
|
"github.com/huin/goupnp"
|
||||||
@ -10,6 +11,36 @@ import (
|
|||||||
|
|
||||||
var spaces = " "
|
var spaces = " "
|
||||||
|
|
||||||
|
type GetExternalIPAddressRequest struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetExternalIPAddressResponse struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
NewExternalIPAddress string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Uint16Value struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
Value uint16 `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetGenericPortMappingEntryRequest struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
NewPortMappingIndex Uint16Value
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetGenericPortMappingEntryResponse struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
NewRemoteHost string
|
||||||
|
NewExternalPort uint16
|
||||||
|
NewProtocol string
|
||||||
|
NewInternalPort uint16
|
||||||
|
NewInternalClient string
|
||||||
|
NewEnabled string // boolean
|
||||||
|
NewLeaseDuration uint32
|
||||||
|
}
|
||||||
|
|
||||||
type indentLevel int
|
type indentLevel int
|
||||||
|
|
||||||
func (i indentLevel) String() string {
|
func (i indentLevel) String() string {
|
||||||
@ -48,12 +79,39 @@ func main() {
|
|||||||
fmt.Printf("Got more than one expected service on device %s\n", device.FriendlyName)
|
fmt.Printf("Got more than one expected service on device %s\n", device.FriendlyName)
|
||||||
}
|
}
|
||||||
srv := wanPPPSrvs[0]
|
srv := wanPPPSrvs[0]
|
||||||
results, err := goupnp.PerformSoapAction(goupnp.ServiceTypeWANPPPConnection, "GetExternalIPAddress", &srv.ControlURL.URL, nil)
|
|
||||||
if err != nil {
|
if scdp, err := srv.RequestSCDP(); err != nil {
|
||||||
fmt.Printf("Failed to GetExternalIPAddress from %s: %v\n", device.FriendlyName, err)
|
fmt.Printf("Error requesting SCPD: %v\n", err)
|
||||||
continue
|
} else {
|
||||||
|
fmt.Println("Available SCPD actions:")
|
||||||
|
for _, action := range scdp.Actions {
|
||||||
|
fmt.Println(" ", action.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
srvClient := goupnp.NewSOAPClient(srv.ControlURL.URL)
|
||||||
|
|
||||||
|
{
|
||||||
|
inAction := GetExternalIPAddressRequest{XMLName: xml.Name{Space: goupnp.ServiceTypeWANPPPConnection, Local: "GetExternalIPAddress"}}
|
||||||
|
var outAction GetExternalIPAddressResponse
|
||||||
|
err := srvClient.PerformAction(goupnp.ServiceTypeWANPPPConnection, "GetExternalIPAddress", &inAction, &outAction)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to GetExternalIPAddress from %s: %v\n", device.FriendlyName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Printf("Got GetExternalIPAddress result from %s: %+v\n", device.FriendlyName, outAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := uint16(0); i < 10; i++ {
|
||||||
|
inAction := GetGenericPortMappingEntryRequest{XMLName: xml.Name{Space: goupnp.ServiceTypeWANPPPConnection, Local: "GetGenericPortMappingEntry"}, NewPortMappingIndex: Uint16Value{XMLName: xml.Name{"", "NewPortMappingIndex"}, Value: i}}
|
||||||
|
var outAction GetGenericPortMappingEntryResponse
|
||||||
|
err := srvClient.PerformAction(goupnp.ServiceTypeWANPPPConnection, "GetGenericPortMappingEntry", &inAction, &outAction)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to GetGenericPortMappingEntry on %s: %v\n", device.FriendlyName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Printf("Got GetGenericPortMappingEntry from %s: %+v\n", device.FriendlyName, outAction)
|
||||||
}
|
}
|
||||||
fmt.Printf("Got GetExternalIPAddress result from %s: %v\n", device.FriendlyName, results)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
115
soap.go
115
soap.go
@ -12,46 +12,29 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SoapEncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/"
|
soapEncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/"
|
||||||
|
soapPrefix = `<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>`
|
||||||
)
|
)
|
||||||
|
|
||||||
type NameValue struct {
|
type SOAPClient struct {
|
||||||
Name string
|
EndpointURL url.URL
|
||||||
Value string
|
HTTPClient http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSoapAction creates a SoapEnvelope with the given action and arguments.
|
func NewSOAPClient(endpointURL url.URL) *SOAPClient {
|
||||||
func newSoapAction(actionNamespace, actionName string, arguments []NameValue) *SoapEnvelope {
|
return &SOAPClient{
|
||||||
env := &SoapEnvelope{
|
EndpointURL: endpointURL,
|
||||||
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.
|
// PerformSOAPAction makes a SOAP request, with the given action.
|
||||||
func PerformSoapAction(actionNamespace, actionName string, url *url.URL, arguments []NameValue) ([]NameValue, error) {
|
func (client *SOAPClient) PerformAction(actionNamespace, actionName string, inAction interface{}, outAction interface{}) error {
|
||||||
requestEnv := newSoapAction(actionNamespace, actionName, arguments)
|
requestBytes, err := encodeRequestAction(inAction)
|
||||||
requestBytes, err := xml.Marshal(requestEnv)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client := http.Client{}
|
response, err := client.HTTPClient.Do(&http.Request{
|
||||||
response, err := client.Do(&http.Request{
|
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
URL: url,
|
URL: &client.EndpointURL,
|
||||||
Header: http.Header{
|
Header: http.Header{
|
||||||
"SOAPACTION": []string{actionNamespace + "#" + actionName},
|
"SOAPACTION": []string{actionNamespace + "#" + actionName},
|
||||||
"CONTENT-TYPE": []string{"text/xml; charset=\"utf-8\""},
|
"CONTENT-TYPE": []string{"text/xml; charset=\"utf-8\""},
|
||||||
@ -61,61 +44,71 @@ func PerformSoapAction(actionNamespace, actionName string, url *url.URL, argumen
|
|||||||
ContentLength: int64(len(requestBytes)),
|
ContentLength: int64(len(requestBytes)),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
if response.StatusCode != 200 {
|
if response.StatusCode != 200 {
|
||||||
return nil, fmt.Errorf("goupnp: SOAP request got %s response from %s", response.Status, url.String())
|
return fmt.Errorf("goupnp: SOAP request got HTTP %s", response.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
responseEnv := newSOAPEnvelope()
|
||||||
decoder := xml.NewDecoder(response.Body)
|
decoder := xml.NewDecoder(response.Body)
|
||||||
var responseEnv SoapEnvelope
|
if err := decoder.Decode(responseEnv); err != nil {
|
||||||
if err := decoder.Decode(&responseEnv); err != nil {
|
return err
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if responseEnv.Body.Fault != nil {
|
if responseEnv.Body.Fault != nil {
|
||||||
return nil, responseEnv.Body.Fault
|
return responseEnv.Body.Fault
|
||||||
}
|
}
|
||||||
|
|
||||||
results := make([]NameValue, len(responseEnv.Body.Action.Arguments))
|
if err := xml.Unmarshal(responseEnv.Body.RawAction, outAction); err != nil {
|
||||||
for i, soapArg := range responseEnv.Body.Action.Arguments {
|
return err
|
||||||
results[i] = NameValue{
|
|
||||||
Name: soapArg.XMLName.Local,
|
|
||||||
Value: soapArg.Value,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return results, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type SoapEnvelope struct {
|
// newSOAPAction creates a soapEnvelope with the given action and arguments.
|
||||||
|
func newSOAPEnvelope() *soapEnvelope {
|
||||||
|
return &soapEnvelope{
|
||||||
|
EncodingStyle: soapEncodingStyle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeRequestAction is a hacky way to create an encoded SOAP envelope
|
||||||
|
// containing the given action. Experiments with one router have shown that it
|
||||||
|
// 500s for requests where the outer default xmlns is set to the SOAP
|
||||||
|
// namespace, and then reassigning the default namespace within that to the
|
||||||
|
// service namespace. Hand-coding the outer XML to work-around this.
|
||||||
|
func encodeRequestAction(inAction interface{}) ([]byte, error) {
|
||||||
|
requestBuf := new(bytes.Buffer)
|
||||||
|
requestBuf.WriteString(soapPrefix)
|
||||||
|
requestEnc := xml.NewEncoder(requestBuf)
|
||||||
|
if err := requestEnc.Encode(inAction); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
requestBuf.WriteString(soapSuffix)
|
||||||
|
return requestBuf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
Body SoapBody `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
|
Body soapBody `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SoapBody struct {
|
type soapBody struct {
|
||||||
Fault *SoapFault `xml:"Fault"`
|
Fault *SOAPFaultError `xml:"Fault"`
|
||||||
Action SoapAction `xml:",any"`
|
RawAction []byte `xml:",innerxml"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SoapFault struct {
|
// SOAPFaultError implements error, and contains SOAP fault information.
|
||||||
|
type SOAPFaultError struct {
|
||||||
FaultCode string `xml:"faultcode"`
|
FaultCode string `xml:"faultcode"`
|
||||||
FaultString string `xml:"faultstring"`
|
FaultString string `xml:"faultstring"`
|
||||||
Detail string `xml:"detail"`
|
Detail string `xml:"detail"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (err *SoapFault) Error() string {
|
func (err *SOAPFaultError) Error() string {
|
||||||
return fmt.Sprintf("SOAP fault: %s", err.FaultString)
|
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"`
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user