// Definition for the SOAP structure required for UPnP's SOAP usage.package soapimport ()const ( soapEncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/" 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>`)typeSOAPClientstruct { EndpointURL url.URL HTTPClient http.Client}func ( url.URL) *SOAPClient {return &SOAPClient{EndpointURL: , }}// PerformSOAPAction makes a SOAP request, with the given action.// inAction and outAction must both be pointers to structs with string fields// only.func ( *SOAPClient) ( context.Context, , string, interface{}, interface{}) error { , := encodeRequestAction(, , )if != nil {return } := &http.Request{Method: "POST",URL: &.EndpointURL,Header: http.Header{"SOAPACTION": []string{`"` + + "#" + + `"`},"CONTENT-TYPE": []string{"text/xml; charset=\"utf-8\""}, },Body: ioutil.NopCloser(bytes.NewBuffer()),// Set ContentLength to avoid chunked encoding - some servers might not support it.ContentLength: int64(len()), } = .WithContext() , := .HTTPClient.Do()if != nil {returnfmt.Errorf("goupnp: error performing SOAP HTTP request: %v", ) }defer .Body.Close()if .StatusCode != 200 && .ContentLength == 0 {returnfmt.Errorf("goupnp: SOAP request got HTTP %s", .Status) } := newSOAPEnvelope() := xml.NewDecoder(.Body)if := .Decode(); != nil {returnfmt.Errorf("goupnp: error decoding response body: %v", ) }if .Body.Fault != nil {return .Body.Fault } elseif .StatusCode != 200 {returnfmt.Errorf("goupnp: SOAP request got HTTP %s", .Status) }if != nil {if := xml.Unmarshal(.Body.RawAction, ); != nil {returnfmt.Errorf("goupnp: error unmarshalling out action: %v, %v", , .Body.RawAction) } }returnnil}// PerformAction is the legacy version of PerformActionCtx, which uses// context.Background.func ( *SOAPClient) (, string, interface{}, interface{}) error {return .PerformActionCtx(context.Background(), , , , )}// 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(, string, interface{}) ([]byte, error) { := new(bytes.Buffer) .WriteString(soapPrefix) .WriteString(`<u:`)xml.EscapeText(, []byte()) .WriteString(` xmlns:u="`)xml.EscapeText(, []byte()) .WriteString(`">`)if != nil {if := encodeRequestArgs(, ); != nil {returnnil, } } .WriteString(`</u:`)xml.EscapeText(, []byte()) .WriteString(`>`) .WriteString(soapSuffix)return .Bytes(), nil}func encodeRequestArgs( *bytes.Buffer, interface{}) error { := reflect.Indirect(reflect.ValueOf())if .Kind() != reflect.Struct {returnfmt.Errorf("goupnp: SOAP inAction is not a struct but of type %v", .Type()) } := xml.NewEncoder() := .NumField() := .Type()for := 0; < ; ++ { := .Field() := .Nameif := .Tag.Get("soap"); != "" { = } := .Field()if .Kind() != reflect.String {returnfmt.Errorf("goupnp: SOAP arg %q is not of type string, but of type %v", , .Type()) } := xml.StartElement{Name: xml.Name{Space: "", Local: }, Attr: nil}if := .EncodeToken(); != nil {returnfmt.Errorf("goupnp: error encoding start element for SOAP arg %q: %v", , ) }if := .Flush(); != nil {returnfmt.Errorf("goupnp: error flushing start element for SOAP arg %q: %v", , ) }if , := .Write([]byte(escapeXMLText(.Interface().(string)))); != nil {returnfmt.Errorf("goupnp: error writing value for SOAP arg %q: %v", , ) }if := .EncodeToken(.End()); != nil {returnfmt.Errorf("goupnp: error encoding end element for SOAP arg %q: %v", , ) } } .Flush()returnnil}var xmlCharRx = regexp.MustCompile("[<>&]")// escapeXMLText is used by generated code to escape text in XML, but only// escaping the characters `<`, `>`, and `&`.//// This is provided in order to work around SOAP server implementations that// fail to decode XML correctly, specifically failing to decode `"`, `'`. Note// that this can only be safely used for injecting into XML text, but not into// attributes or other contexts.func escapeXMLText( string) string {returnxmlCharRx.ReplaceAllStringFunc(, replaceEntity)}func replaceEntity( string) string {switch {case"<":return"<"case">":return">"case"&":return"&" }return}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 *SOAPFaultError`xml:"Fault"` RawAction []byte`xml:",innerxml"`}// SOAPFaultError implements error, and contains SOAP fault information.typeSOAPFaultErrorstruct { FaultCode string`xml:"faultcode"` FaultString string`xml:"faultstring"` Detail struct { UPnPError struct { Errorcode int`xml:"errorCode"` ErrorDescription string`xml:"errorDescription"` } `xml:"UPnPError"` Raw []byte`xml:",innerxml"` } `xml:"detail"`}func ( *SOAPFaultError) () string {returnfmt.Sprintf("SOAP fault. Code: %s | Explanation: %s | Detail: %s", .FaultCode, .FaultString, string(.Detail.Raw))}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.