Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support for SOAP 1.2 and broken web-services #134

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 172 additions & 18 deletions soap/soap.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,153 @@ import (
"bytes"
"crypto/tls"
"encoding/xml"
"errors"
"io/ioutil"
"net"
"net/http"
"time"
)

type SOAPEnvelope struct {
// supported SOAP versions
const (
SOAP11 = 11
SOAP12 = 12
)

// SOAPVersion determines which version of SOAP protocol to use for requests
type SOAPVersion int

// SOAPEnvelope is general representation of 3 types supported XML SOAP Envelopes
// for SOAP 1.1, 1.2 and custom user version
type SOAPEnvelope interface {
SetHeaders([]interface{})
SetContent(content interface{})
Fault() *SOAPFault
}

// newSOAPEnvelope produces new SOAPEnvelope struct according passed params
func newSOAPEnvelope(c *Client, request bool) (SOAPEnvelope, error) {
// if user provided callbacks for custom constructors, use them
if request && c.opts.customReq != nil {
return c.opts.customReq(), nil
}
if !request && c.opts.customResp != nil {
return c.opts.customResp(), nil
}

// overwise use default for standard headers
switch c.opts.version {
case SOAP11:
return new(SOAPEnvelope11), nil
case SOAP12:
return new(SOAPEnvelope12), nil
}

return nil, errors.New("version is not supported")
}

type SOAPEnvelope11 struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
Headers []interface{} `xml:"http://schemas.xmlsoap.org/soap/envelope/ Header"`
Body SOAPBody
Body SOAPBody11
}

type SOAPBody struct {
func (s *SOAPEnvelope11) SetHeaders(headers []interface{}) {
s.Headers = headers
}

func (s *SOAPEnvelope11) SetContent(content interface{}) {
s.Body = SOAPBody11{Content: content}
}

func (s SOAPEnvelope11) Fault() *SOAPFault {
return s.Body.Fault
}

type SOAPEnvelope12 struct {
XMLName xml.Name `xml:"http://www.w3.org/2003/05/soap-envelope Envelope"`
Headers []interface{} `xml:"http://www.w3.org/2003/05/soap-envelope Header"`
Body SOAPBody12
}

func (s *SOAPEnvelope12) SetHeaders(headers []interface{}) {
s.Headers = headers
}

func (s *SOAPEnvelope12) SetContent(content interface{}) {
s.Body = SOAPBody12{Content: content}
}

func (s SOAPEnvelope12) Fault() *SOAPFault {
return s.Body.Fault
}

type SOAPBody11 struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`

Fault *SOAPFault `xml:",omitempty"`
Content interface{} `xml:",omitempty"`
}

// UnmarshalXML unmarshals SOAPBody xml
func (b *SOAPBody) UnmarshalXML(d *xml.Decoder, _ xml.StartElement) error {
if b.Content == nil {
func (s SOAPBody11) content() interface{} {
return s.Content
}

func (s *SOAPBody11) setContent(c interface{}) {
s.Content = c
}

func (s SOAPBody11) fault() *SOAPFault {
return s.Fault
}

func (s *SOAPBody11) setFault(f *SOAPFault) {
s.Fault = f
}

type SOAPBody interface {
content() interface{}
setContent(interface{})
fault() *SOAPFault
setFault(*SOAPFault)
}

type SOAPBody12 struct {
XMLName xml.Name `xml:"http://www.w3.org/2003/05/soap-envelope Body"`

Fault *SOAPFault `xml:",omitempty"`
Content interface{} `xml:",omitempty"`
}

func (s SOAPBody12) content() interface{} {
return s.Content
}

func (s *SOAPBody12) setContent(c interface{}) {
s.Content = c
}

func (s SOAPBody12) fault() *SOAPFault {
return s.Fault
}

func (s *SOAPBody12) setFault(f *SOAPFault) {
s.Fault = f
}

// UnmarshalXML unmarshals SOAPBody11 xml
func (b *SOAPBody11) UnmarshalXML(d *xml.Decoder, _ xml.StartElement) error {
return unmarshalSOAPBody(d, b)
}

// UnmarshalXML unmarshals SOAPBody12 xml
func (b *SOAPBody12) UnmarshalXML(d *xml.Decoder, _ xml.StartElement) error {
return unmarshalSOAPBody(d, b)
}

// unmarshalSOAPBody is a common code for both SOAP11 and SOAP12 unmarshal funcs
func unmarshalSOAPBody(d *xml.Decoder, b SOAPBody) error {
if b.content() == nil {
return xml.UnmarshalError("Content must be a pointer to a struct")
}

Expand All @@ -50,17 +175,17 @@ Loop:
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
b.setFault(&SOAPFault{})
b.setContent(nil)

err = d.DecodeElement(b.Fault, &se)
err = d.DecodeElement(b.fault(), &se)
if err != nil {
return err
}

consumed = true
} else {
if err = d.DecodeElement(b.Content, &se); err != nil {
if err = d.DecodeElement(b.content(), &se); err != nil {
return err
}

Expand Down Expand Up @@ -151,12 +276,16 @@ type options struct {
tlshshaketimeout time.Duration
client HTTPClient
httpHeaders map[string]string
version SOAPVersion
customReq func() SOAPEnvelope
customResp func() SOAPEnvelope
}

var defaultOptions = options{
timeout: time.Duration(30 * time.Second),
contimeout: time.Duration(90 * time.Second),
tlshshaketimeout: time.Duration(15 * time.Second),
version: SOAP11,
}

// A Option sets options such as credentials, tls, etc.
Expand Down Expand Up @@ -216,6 +345,21 @@ func WithHTTPHeaders(headers map[string]string) Option {
}
}

// WithSOAPVersion is an Option to set SOAP protocol version to use
func WithSOAPVersion(version SOAPVersion) Option {
return func(o *options) {
o.version = version
}
}

// WithCustomRequester is an Option to set specific SOAP Envelope contructor for
// broken services (used only for send requests)
func WithCustomRequester(req func() SOAPEnvelope) Option {
return func(o *options) {
o.customReq = req
}
}

// Client is soap client
type Client struct {
url string
Expand Down Expand Up @@ -248,15 +392,17 @@ func (s *Client) AddHeader(header interface{}) {

// Call performs HTTP POST request
func (s *Client) Call(soapAction string, request, response interface{}) error {
envelope := SOAPEnvelope{}
envelope, err := newSOAPEnvelope(s, true /* request */)
if err != nil {
return nil
}

if s.headers != nil && len(s.headers) > 0 {
envelope.Headers = s.headers
envelope.SetHeaders(s.headers)
}

envelope.Body.Content = request
envelope.SetContent(request)
buffer := new(bytes.Buffer)

encoder := xml.NewEncoder(buffer)

if err := encoder.Encode(envelope); err != nil {
Expand All @@ -275,7 +421,12 @@ func (s *Client) Call(soapAction string, request, response interface{}) error {
req.SetBasicAuth(s.opts.auth.Login, s.opts.auth.Password)
}

req.Header.Add("Content-Type", "text/xml; charset=\"utf-8\"")
switch s.opts.version {
case SOAP11:
req.Header.Add("Content-Type", "text/xml; charset=\"utf-8\"")
case SOAP12:
req.Header.Add("Content-Type", "application/soap+xml")
}
req.Header.Add("SOAPAction", soapAction)
req.Header.Set("User-Agent", "gowsdl/0.1")
if s.opts.httpHeaders != nil {
Expand Down Expand Up @@ -311,14 +462,17 @@ func (s *Client) Call(soapAction string, request, response interface{}) error {
return nil
}

respEnvelope := new(SOAPEnvelope)
respEnvelope.Body = SOAPBody{Content: response}
respEnvelope, err := newSOAPEnvelope(s, false /* request */)
if err != nil {
return nil
}
respEnvelope.SetContent(response)
err = xml.Unmarshal(rawbody, respEnvelope)
if err != nil {
return err
}

fault := respEnvelope.Body.Fault
fault := respEnvelope.Fault()
if fault != nil {
return fault
}
Expand Down