Mercurial > gemma
comparison 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 |
comparison
equal
deleted
inserted
replaced
565:4bc27eea4f09 | 566:5e45f441aed6 |
---|---|
1 package soap | |
2 | |
3 import ( | |
4 "bytes" | |
5 "crypto/tls" | |
6 "encoding/xml" | |
7 "io/ioutil" | |
8 "log" | |
9 "math/rand" | |
10 "net" | |
11 "net/http" | |
12 "time" | |
13 ) | |
14 | |
15 const timeout = time.Duration(30 * time.Second) | |
16 | |
17 func dialTimeout(network, addr string) (net.Conn, error) { | |
18 return net.DialTimeout(network, addr, timeout) | |
19 } | |
20 | |
21 type SOAPEnvelope struct { | |
22 XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` | |
23 Header *SOAPHeader | |
24 Body SOAPBody | |
25 } | |
26 | |
27 type SOAPHeader struct { | |
28 XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Header"` | |
29 | |
30 Items []interface{} `xml:",omitempty"` | |
31 } | |
32 | |
33 type SOAPBody struct { | |
34 XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"` | |
35 | |
36 Fault *SOAPFault `xml:",omitempty"` | |
37 Content interface{} `xml:",omitempty"` | |
38 } | |
39 | |
40 type SOAPFault struct { | |
41 XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault"` | |
42 | |
43 Code string `xml:"faultcode,omitempty"` | |
44 String string `xml:"faultstring,omitempty"` | |
45 Actor string `xml:"faultactor,omitempty"` | |
46 Detail string `xml:"detail,omitempty"` | |
47 } | |
48 | |
49 const ( | |
50 // Predefined WSS namespaces to be used in | |
51 WssNsWSSE string = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" | |
52 WssNsWSU string = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" | |
53 WssNsType string = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText" | |
54 ) | |
55 | |
56 type WSSSecurityHeader struct { | |
57 XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ wsse:Security"` | |
58 XmlNSWsse string `xml:"xmlns:wsse,attr"` | |
59 | |
60 MustUnderstand string `xml:"mustUnderstand,attr,omitempty"` | |
61 | |
62 Token *WSSUsernameToken `xml:",omitempty"` | |
63 } | |
64 | |
65 type WSSUsernameToken struct { | |
66 XMLName xml.Name `xml:"wsse:UsernameToken"` | |
67 XmlNSWsu string `xml:"xmlns:wsu,attr"` | |
68 XmlNSWsse string `xml:"xmlns:wsse,attr"` | |
69 | |
70 Id string `xml:"wsu:Id,attr,omitempty"` | |
71 | |
72 Username *WSSUsername `xml:",omitempty"` | |
73 Password *WSSPassword `xml:",omitempty"` | |
74 } | |
75 | |
76 type WSSUsername struct { | |
77 XMLName xml.Name `xml:"wsse:Username"` | |
78 XmlNSWsse string `xml:"xmlns:wsse,attr"` | |
79 | |
80 Data string `xml:",chardata"` | |
81 } | |
82 | |
83 type WSSPassword struct { | |
84 XMLName xml.Name `xml:"wsse:Password"` | |
85 XmlNSWsse string `xml:"xmlns:wsse,attr"` | |
86 XmlNSType string `xml:"Type,attr"` | |
87 | |
88 Data string `xml:",chardata"` | |
89 } | |
90 | |
91 type BasicAuth struct { | |
92 Login string | |
93 Password string | |
94 } | |
95 | |
96 type SOAPClient struct { | |
97 url string | |
98 tlsCfg *tls.Config | |
99 auth *BasicAuth | |
100 headers []interface{} | |
101 } | |
102 | |
103 // ********** | |
104 // Accepted solution from http://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang | |
105 // Author: Icza - http://stackoverflow.com/users/1705598/icza | |
106 | |
107 const ( | |
108 letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" | |
109 letterIdxBits = 6 // 6 bits to represent a letter index | |
110 letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits | |
111 letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits | |
112 ) | |
113 | |
114 func randStringBytesMaskImprSrc(n int) string { | |
115 src := rand.NewSource(time.Now().UnixNano()) | |
116 b := make([]byte, n) | |
117 // A src.Int63() generates 63 random bits, enough for letterIdxMax characters! | |
118 for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; { | |
119 if remain == 0 { | |
120 cache, remain = src.Int63(), letterIdxMax | |
121 } | |
122 if idx := int(cache & letterIdxMask); idx < len(letterBytes) { | |
123 b[i] = letterBytes[idx] | |
124 i-- | |
125 } | |
126 cache >>= letterIdxBits | |
127 remain-- | |
128 } | |
129 return string(b) | |
130 } | |
131 | |
132 // ********** | |
133 | |
134 func NewWSSSecurityHeader(user, pass, mustUnderstand string) *WSSSecurityHeader { | |
135 hdr := &WSSSecurityHeader{XmlNSWsse: WssNsWSSE, MustUnderstand: mustUnderstand} | |
136 hdr.Token = &WSSUsernameToken{XmlNSWsu: WssNsWSU, XmlNSWsse: WssNsWSSE, Id: "UsernameToken-" + randStringBytesMaskImprSrc(9)} | |
137 hdr.Token.Username = &WSSUsername{XmlNSWsse: WssNsWSSE, Data: user} | |
138 hdr.Token.Password = &WSSPassword{XmlNSWsse: WssNsWSSE, XmlNSType: WssNsType, Data: pass} | |
139 return hdr | |
140 } | |
141 | |
142 func (b *SOAPBody) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { | |
143 if b.Content == nil { | |
144 return xml.UnmarshalError("Content must be a pointer to a struct") | |
145 } | |
146 | |
147 var ( | |
148 token xml.Token | |
149 err error | |
150 consumed bool | |
151 ) | |
152 | |
153 Loop: | |
154 for { | |
155 if token, err = d.Token(); err != nil { | |
156 return err | |
157 } | |
158 | |
159 if token == nil { | |
160 break | |
161 } | |
162 | |
163 switch se := token.(type) { | |
164 case xml.StartElement: | |
165 if consumed { | |
166 return xml.UnmarshalError("Found multiple elements inside SOAP body; not wrapped-document/literal WS-I compliant") | |
167 } else if se.Name.Space == "http://schemas.xmlsoap.org/soap/envelope/" && se.Name.Local == "Fault" { | |
168 b.Fault = &SOAPFault{} | |
169 b.Content = nil | |
170 | |
171 err = d.DecodeElement(b.Fault, &se) | |
172 if err != nil { | |
173 return err | |
174 } | |
175 | |
176 consumed = true | |
177 } else { | |
178 if err = d.DecodeElement(b.Content, &se); err != nil { | |
179 return err | |
180 } | |
181 | |
182 consumed = true | |
183 } | |
184 case xml.EndElement: | |
185 break Loop | |
186 } | |
187 } | |
188 | |
189 return nil | |
190 } | |
191 | |
192 func (f *SOAPFault) Error() string { | |
193 return f.String | |
194 } | |
195 | |
196 func NewSOAPClient(url string, insecureSkipVerify bool, auth *BasicAuth) *SOAPClient { | |
197 tlsCfg := &tls.Config{ | |
198 InsecureSkipVerify: insecureSkipVerify, | |
199 } | |
200 return NewSOAPClientWithTLSConfig(url, tlsCfg, auth) | |
201 } | |
202 | |
203 func NewSOAPClientWithTLSConfig(url string, tlsCfg *tls.Config, auth *BasicAuth) *SOAPClient { | |
204 return &SOAPClient{ | |
205 url: url, | |
206 tlsCfg: tlsCfg, | |
207 auth: auth, | |
208 } | |
209 } | |
210 | |
211 func (s *SOAPClient) AddHeader(header interface{}) { | |
212 s.headers = append(s.headers, header) | |
213 } | |
214 | |
215 func (s *SOAPClient) Call(soapAction string, request, response interface{}) error { | |
216 envelope := SOAPEnvelope{} | |
217 | |
218 if s.headers != nil && len(s.headers) > 0 { | |
219 soapHeader := &SOAPHeader{Items: make([]interface{}, len(s.headers))} | |
220 copy(soapHeader.Items, s.headers) | |
221 envelope.Header = soapHeader | |
222 } | |
223 | |
224 envelope.Body.Content = request | |
225 buffer := new(bytes.Buffer) | |
226 | |
227 encoder := xml.NewEncoder(buffer) | |
228 //encoder.Indent("", " ") | |
229 | |
230 if err := encoder.Encode(envelope); err != nil { | |
231 return err | |
232 } | |
233 | |
234 if err := encoder.Flush(); err != nil { | |
235 return err | |
236 } | |
237 | |
238 //log.Println(buffer.String()) | |
239 | |
240 req, err := http.NewRequest("POST", s.url, buffer) | |
241 if err != nil { | |
242 return err | |
243 } | |
244 if s.auth != nil { | |
245 req.SetBasicAuth(s.auth.Login, s.auth.Password) | |
246 } | |
247 | |
248 req.Header.Add("Content-Type", "text/xml; charset=\"utf-8\"") | |
249 req.Header.Add("SOAPAction", soapAction) | |
250 | |
251 req.Header.Set("User-Agent", "gowsdl/0.1") | |
252 req.Close = true | |
253 | |
254 tr := &http.Transport{ | |
255 TLSClientConfig: s.tlsCfg, | |
256 Dial: dialTimeout, | |
257 } | |
258 | |
259 client := &http.Client{Transport: tr} | |
260 res, err := client.Do(req) | |
261 if err != nil { | |
262 return err | |
263 } | |
264 defer res.Body.Close() | |
265 | |
266 rawbody, err := ioutil.ReadAll(res.Body) | |
267 if err != nil { | |
268 return err | |
269 } | |
270 if len(rawbody) == 0 { | |
271 log.Println("empty response") | |
272 return nil | |
273 } | |
274 | |
275 //log.Println(string(rawbody)) | |
276 respEnvelope := new(SOAPEnvelope) | |
277 respEnvelope.Body = SOAPBody{Content: response} | |
278 err = xml.Unmarshal(rawbody, respEnvelope) | |
279 if err != nil { | |
280 return err | |
281 } | |
282 | |
283 fault := respEnvelope.Body.Fault | |
284 if fault != nil { | |
285 return fault | |
286 } | |
287 | |
288 return nil | |
289 } |