Mercurial > gemma
diff pkg/soap/soap.go @ 566:5e45f441aed6
Added SOAP client for the IFBN bottleneck service.
Generated by github.com/hooklift/gowsdl from official WSDL (2018-09-03)
and edit by hand to resolve some problems.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Tue, 04 Sep 2018 17:13:25 +0200 |
parents | |
children | a244b18cb916 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/soap/soap.go Tue Sep 04 17:13:25 2018 +0200 @@ -0,0 +1,289 @@ +package soap + +import ( + "bytes" + "crypto/tls" + "encoding/xml" + "io/ioutil" + "log" + "math/rand" + "net" + "net/http" + "time" +) + +const timeout = time.Duration(30 * time.Second) + +func dialTimeout(network, addr string) (net.Conn, error) { + return net.DialTimeout(network, addr, timeout) +} + +type SOAPEnvelope struct { + XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` + Header *SOAPHeader + Body SOAPBody +} + +type SOAPHeader struct { + XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Header"` + + Items []interface{} `xml:",omitempty"` +} + +type SOAPBody struct { + XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"` + + Fault *SOAPFault `xml:",omitempty"` + Content interface{} `xml:",omitempty"` +} + +type SOAPFault struct { + XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault"` + + Code string `xml:"faultcode,omitempty"` + String string `xml:"faultstring,omitempty"` + Actor string `xml:"faultactor,omitempty"` + Detail string `xml:"detail,omitempty"` +} + +const ( + // Predefined WSS namespaces to be used in + WssNsWSSE string = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" + WssNsWSU string = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" + WssNsType string = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText" +) + +type WSSSecurityHeader struct { + XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ wsse:Security"` + XmlNSWsse string `xml:"xmlns:wsse,attr"` + + MustUnderstand string `xml:"mustUnderstand,attr,omitempty"` + + Token *WSSUsernameToken `xml:",omitempty"` +} + +type WSSUsernameToken struct { + XMLName xml.Name `xml:"wsse:UsernameToken"` + XmlNSWsu string `xml:"xmlns:wsu,attr"` + XmlNSWsse string `xml:"xmlns:wsse,attr"` + + Id string `xml:"wsu:Id,attr,omitempty"` + + Username *WSSUsername `xml:",omitempty"` + Password *WSSPassword `xml:",omitempty"` +} + +type WSSUsername struct { + XMLName xml.Name `xml:"wsse:Username"` + XmlNSWsse string `xml:"xmlns:wsse,attr"` + + Data string `xml:",chardata"` +} + +type WSSPassword struct { + XMLName xml.Name `xml:"wsse:Password"` + XmlNSWsse string `xml:"xmlns:wsse,attr"` + XmlNSType string `xml:"Type,attr"` + + Data string `xml:",chardata"` +} + +type BasicAuth struct { + Login string + Password string +} + +type SOAPClient struct { + url string + tlsCfg *tls.Config + auth *BasicAuth + headers []interface{} +} + +// ********** +// Accepted solution from http://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang +// Author: Icza - http://stackoverflow.com/users/1705598/icza + +const ( + letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits + letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits +) + +func randStringBytesMaskImprSrc(n int) string { + src := rand.NewSource(time.Now().UnixNano()) + b := make([]byte, n) + // A src.Int63() generates 63 random bits, enough for letterIdxMax characters! + for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; { + if remain == 0 { + cache, remain = src.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + b[i] = letterBytes[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + return string(b) +} + +// ********** + +func NewWSSSecurityHeader(user, pass, mustUnderstand string) *WSSSecurityHeader { + hdr := &WSSSecurityHeader{XmlNSWsse: WssNsWSSE, MustUnderstand: mustUnderstand} + hdr.Token = &WSSUsernameToken{XmlNSWsu: WssNsWSU, XmlNSWsse: WssNsWSSE, Id: "UsernameToken-" + randStringBytesMaskImprSrc(9)} + hdr.Token.Username = &WSSUsername{XmlNSWsse: WssNsWSSE, Data: user} + hdr.Token.Password = &WSSPassword{XmlNSWsse: WssNsWSSE, XmlNSType: WssNsType, Data: pass} + return hdr +} + +func (b *SOAPBody) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + if b.Content == nil { + return xml.UnmarshalError("Content must be a pointer to a struct") + } + + var ( + token xml.Token + err error + consumed bool + ) + +Loop: + for { + if token, err = d.Token(); err != nil { + return err + } + + if token == nil { + break + } + + switch se := token.(type) { + case xml.StartElement: + if consumed { + return xml.UnmarshalError("Found multiple elements inside SOAP body; not wrapped-document/literal WS-I compliant") + } else if se.Name.Space == "http://schemas.xmlsoap.org/soap/envelope/" && se.Name.Local == "Fault" { + b.Fault = &SOAPFault{} + b.Content = nil + + err = d.DecodeElement(b.Fault, &se) + if err != nil { + return err + } + + consumed = true + } else { + if err = d.DecodeElement(b.Content, &se); err != nil { + return err + } + + consumed = true + } + case xml.EndElement: + break Loop + } + } + + return nil +} + +func (f *SOAPFault) Error() string { + return f.String +} + +func NewSOAPClient(url string, insecureSkipVerify bool, auth *BasicAuth) *SOAPClient { + tlsCfg := &tls.Config{ + InsecureSkipVerify: insecureSkipVerify, + } + return NewSOAPClientWithTLSConfig(url, tlsCfg, auth) +} + +func NewSOAPClientWithTLSConfig(url string, tlsCfg *tls.Config, auth *BasicAuth) *SOAPClient { + return &SOAPClient{ + url: url, + tlsCfg: tlsCfg, + auth: auth, + } +} + +func (s *SOAPClient) AddHeader(header interface{}) { + s.headers = append(s.headers, header) +} + +func (s *SOAPClient) Call(soapAction string, request, response interface{}) error { + envelope := SOAPEnvelope{} + + if s.headers != nil && len(s.headers) > 0 { + soapHeader := &SOAPHeader{Items: make([]interface{}, len(s.headers))} + copy(soapHeader.Items, s.headers) + envelope.Header = soapHeader + } + + envelope.Body.Content = request + buffer := new(bytes.Buffer) + + encoder := xml.NewEncoder(buffer) + //encoder.Indent("", " ") + + if err := encoder.Encode(envelope); err != nil { + return err + } + + if err := encoder.Flush(); err != nil { + return err + } + + //log.Println(buffer.String()) + + req, err := http.NewRequest("POST", s.url, buffer) + if err != nil { + return err + } + if s.auth != nil { + req.SetBasicAuth(s.auth.Login, s.auth.Password) + } + + req.Header.Add("Content-Type", "text/xml; charset=\"utf-8\"") + req.Header.Add("SOAPAction", soapAction) + + req.Header.Set("User-Agent", "gowsdl/0.1") + req.Close = true + + tr := &http.Transport{ + TLSClientConfig: s.tlsCfg, + Dial: dialTimeout, + } + + client := &http.Client{Transport: tr} + res, err := client.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + rawbody, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + if len(rawbody) == 0 { + log.Println("empty response") + return nil + } + + //log.Println(string(rawbody)) + respEnvelope := new(SOAPEnvelope) + respEnvelope.Body = SOAPBody{Content: response} + err = xml.Unmarshal(rawbody, respEnvelope) + if err != nil { + return err + } + + fault := respEnvelope.Body.Fault + if fault != nil { + return fault + } + + return nil +}