在Go语言中支持WSDL/SOAP吗?

huangapple go评论80阅读模式
英文:

WSDL/SOAP support on Go?

问题

有没有支持Go语言的SOAP/WSDL的包?

英文:

Are there any packages to support SOAP/WSDL on Go?

答案1

得分: 24

在Go语言中没有对WSDL的支持。其他语言中的支持要么是静态的,要么是动态的:要么结构体是从WSDL预先生成的,要么是使用哈希表动态生成的。

然而,您可以手动编码和解码SOAP请求。我发现标准的encoding/xml包对于SOAP来说是不够的。不同服务器中存在许多怪癖,并且encoding/xml的限制使得很难生成这些服务器满意的请求。

例如,某些服务器需要在每个字符串标签上添加xsi:type="xsd:string"。为了正确实现这一点,您的结构体需要像这样使用encoding/xml

type MethodCall struct {
    One XSI
    Two XSI
}

type XSI struct {
    Type string `xml:"xsi:type,attr"`
    Value string `xml:",chardata"`
}

并且您可以像这样构造它:

MethodCall{
    XSI{"xsd:string", "One"},
    XSI{"xsd:string", "Two"},
}

这将给出以下结果:

<MethodCall>
    <One xsi:type="xsd:string">One</One>
    <Two xsi:type="xsd:string">Two</Two>
</MethodCall>

现在这可能还可以。它肯定能完成工作。但是,如果您需要的不仅仅是一个string呢?encoding/xml目前不支持interface{}

正如您所看到的,这变得复杂了。如果您只有一个要集成的SOAP API,这可能还好。但是如果您有多个,每个都有自己的怪癖呢?

如果您可以像这样做会不会很好呢?

type MethodCall struct {
    One string
    Two string
}

然后告诉encoding/xml:“这个服务器需要xsi类型”。

为了解决这个问题,我创建了github.com/webconnex/xmlutil。它还在不断改进中。它没有encoding/xml编码器/解码器的所有功能,但它具备处理SOAP所需的功能。

这是一个可工作的示例:

package main

import (
    "bytes"
    "encoding/xml"
    "fmt"
    "github.com/webconnex/xmlutil"
    "log"
    //"net/http"
)

type Envelope struct {
    Body `xml:"soap:"`
}

type Body struct {
    Msg interface{}
}

type MethodCall struct {
    One string
    Two string
}

type MethodCallResponse struct {
    Three string
}

func main() {
    x := xmlutil.NewXmlUtil()
    x.RegisterNamespace("http://www.w3.org/2001/XMLSchema-instance", "xsi")
    x.RegisterNamespace("http://www.w3.org/2001/XMLSchema", "xsd")
    x.RegisterNamespace("http://www.w3.org/2003/05/soap-envelope", "soap")
    x.RegisterTypeMore(Envelope{}, xml.Name{"http://www.w3.org/2003/05/soap-envelope", ""},
        []xml.Attr{
            xml.Attr{xml.Name{"xmlns", "xsi"}, "http://www.w3.org/2001/XMLSchema-instance"},
            xml.Attr{xml.Name{"xmlns", "xsd"}, "http://www.w3.org/2001/XMLSchema"},
            xml.Attr{xml.Name{"xmlns", "soap"}, "http://www.w3.org/2003/05/soap-envelope"},
        })
    x.RegisterTypeMore("", xml.Name{}, []xml.Attr{
        xml.Attr{xml.Name{"http://www.w3.org/2001/XMLSchema-instance", "type"}, "xsd:string"},
    })

    buf := new(bytes.Buffer)
    buf.WriteString(`<?xml version="1.0" encoding="utf-8"?>`)
    buf.WriteByte('\n')
    enc := x.NewEncoder(buf)
    env := &Envelope{Body{MethodCall{
        One: "one",
        Two: "two",
    }}}
    if err := enc.Encode(env); err != nil {
        log.Fatal(err)
    }
    // Print request
    bs := buf.Bytes()
    bs = bytes.Replace(bs, []byte{'>', '<'}, []byte{'>', '\n', '<'}, -1)
    fmt.Printf("%s\n\n", bs)

    /*
        // Send response, SOAP 1.2, fill in url, namespace, and action
        var r *http.Response
        if r, err = http.Post(url, "application/soap+xml; charset=utf-8; action="+namespace+"/"+action, buf); err != nil {
            return
        }
        dec := x.NewDecoder(r.Body)
    */
    // Decode response
    dec := x.NewDecoder(bytes.NewBufferString(`<?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope>
        <soap:Body>
            <MethodCallResponse>
                <Three>three</Three>
            </MethodCallResponse>
        </soap:Body>
    </soap:Envelope>`))
    find := []xml.Name{
        xml.Name{"", "MethodCallResponse"},
        xml.Name{"http://www.w3.org/2003/05/soap-envelope", "Fault"},
    }
    var start *xml.StartElement
    var err error
    if start, err = dec.Find(find); err != nil {
        log.Fatal(err)
    }
    if start.Name.Local == "Fault" {
        log.Fatal("Fault!") // Here you can decode a Soap Fault
    }
    var resp MethodCallResponse
    if err := dec.DecodeElement(&resp, start); err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%#v\n\n", resp)
}

在上面的示例中,我使用Find方法获取响应对象或故障。这并不是绝对必要的。您也可以这样做:

x.RegisterType(MethodCallResponse{})
...
// Decode response
dec := x.NewDecoder(bytes.NewBufferString(`<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope>
    <soap:Body>
        <MethodCallResponse>
            <Three>three</Three>
        </MethodCallResponse>
    </soap:Body>
</soap:Envelope>`))
var start *xml.StartElement
var resp Envelope
if err := dec.DecodeElement(&resp, start); err != nil {
    log.Fatal(err)
}
fmt.Printf("%#v\n\n", resp)

当您的数据如下所示时,您会发现Find方法很有用:

<soap:Envelope>
  <soap:Body>
    <MethodResponse>
      <MethodResult>
        <diffgr:diffgram>
          <NewDataSet>
            <Table1 diffgr:id="Table1" msdata:rowOrder="0" diffgr:hasChanges="inserted">
              <Three>three</Three>
            </Table1>
          </NewDataSet>
        </diffgr:diffgram>
      </MethodResult>
    </MethodResponse>
  </soap:Body>
</soap:Envelope>

这是Microsoft .NET的DiffGram的一部分。您可以使用Find方法获取到Table1DecodeDecodeElement方法也适用于切片。因此,如果NewDataSet包含多个结果,您可以传入[]MethodCallResponse

我同意Zippower的观点,SOAP确实很糟糕。但不幸的是,许多企业使用SOAP,有时您被迫使用这些API。通过xmlutil包,我希望能够减轻与之工作的痛苦。

英文:

There isn't support for WSDL in Go. Support in other languages are either static or dynamic: Either structs are pre-generated from the WSDL, or it's done on the fly with hash tables.

You can, however, encode and decode SOAP requests manually. I found that the standard encoding/xml package to be insufficient for SOAP. There are so many quirks in different servers, and the limitations in encoding/xml make it difficult generate a request these servers are happy with.

For example, some servers need xsi:type=&quot;xsd:string&quot; on every string tag. In order to do this properly your struct needs to look like this for encoding/xml:

type MethodCall struct {
	One XSI
	Two XSI
}

type XSI struct {
	Type string `xml:&quot;xsi:type,attr&quot;`
	Vaue string `xml:&quot;,chardata&quot;`
}

And you construct it like this:

MethodCall{
	XSI{&quot;xsd:string&quot;, &quot;One&quot;},
	XSI{&quot;xsd:string&quot;, &quot;Two&quot;},
}

Which gives you:

&lt;MethodCall&gt;
	&lt;One xsi:type=&quot;xsd:string&quot;&gt;One&lt;/One&gt;
	&lt;Two xsi:type=&quot;xsd:string&quot;&gt;Two&lt;/Two&gt;
&lt;/MethodCall&gt;

Now this might be ok. It certainly gets the job done. But what if you needed more than just a string? encoding/xml currently doesn't support interface{}.

As you can see this gets complicated. If you had one SOAP API to integrate, this probably wouldn't be too bad. What if you had several, each with their own quirks?

Wouldn't it be nice if you could just do this?

type MethodCall struct {
    One string
    Two string
}

Then say to encoding/xml: "This server want xsi types".

To solve this problem I created github.com/webconnex/xmlutil. It's a work in progress. It doesn't have all the features of encoding/xml's encoder/decoder, but it has what is needed for SOAP.

Here's a working example:

package main

import (
	&quot;bytes&quot;
	&quot;encoding/xml&quot;
	&quot;fmt&quot;
	&quot;github.com/webconnex/xmlutil&quot;
	&quot;log&quot;
	//&quot;net/http&quot;
)

type Envelope struct {
	Body `xml:&quot;soap:&quot;`
}

type Body struct {
	Msg interface{}
}

type MethodCall struct {
	One string
	Two string
}

type MethodCallResponse struct {
	Three string
}

func main() {
	x := xmlutil.NewXmlUtil()
	x.RegisterNamespace(&quot;http://www.w3.org/2001/XMLSchema-instance&quot;, &quot;xsi&quot;)
	x.RegisterNamespace(&quot;http://www.w3.org/2001/XMLSchema&quot;, &quot;xsd&quot;)
	x.RegisterNamespace(&quot;http://www.w3.org/2003/05/soap-envelope&quot;, &quot;soap&quot;)
	x.RegisterTypeMore(Envelope{}, xml.Name{&quot;http://www.w3.org/2003/05/soap-envelope&quot;, &quot;&quot;},
		[]xml.Attr{
			xml.Attr{xml.Name{&quot;xmlns&quot;, &quot;xsi&quot;}, &quot;http://www.w3.org/2001/XMLSchema-instance&quot;},
			xml.Attr{xml.Name{&quot;xmlns&quot;, &quot;xsd&quot;}, &quot;http://www.w3.org/2001/XMLSchema&quot;},
			xml.Attr{xml.Name{&quot;xmlns&quot;, &quot;soap&quot;}, &quot;http://www.w3.org/2003/05/soap-envelope&quot;},
		})
	x.RegisterTypeMore(&quot;&quot;, xml.Name{}, []xml.Attr{
		xml.Attr{xml.Name{&quot;http://www.w3.org/2001/XMLSchema-instance&quot;, &quot;type&quot;}, &quot;xsd:string&quot;},
	})

	buf := new(bytes.Buffer)
	buf.WriteString(`&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;`)
	buf.WriteByte(&#39;\n&#39;)
	enc := x.NewEncoder(buf)
	env := &amp;Envelope{Body{MethodCall{
		One: &quot;one&quot;,
		Two: &quot;two&quot;,
	}}}
	if err := enc.Encode(env); err != nil {
		log.Fatal(err)
	}
	// Print request
	bs := buf.Bytes()
	bs = bytes.Replace(bs, []byte{&#39;&gt;&#39;, &#39;&lt;&#39;}, []byte{&#39;&gt;&#39;, &#39;\n&#39;, &#39;&lt;&#39;}, -1)
	fmt.Printf(&quot;%s\n\n&quot;, bs)

	/*
		// Send response, SOAP 1.2, fill in url, namespace, and action
		var r *http.Response
		if r, err = http.Post(url, &quot;application/soap+xml; charset=utf-8; action=&quot;+namespace+&quot;/&quot;+action, buf); err != nil {
			return
		}
		dec := x.NewDecoder(r.Body)
	*/
	// Decode response
	dec := x.NewDecoder(bytes.NewBufferString(`&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
	&lt;soap:Envelope&gt;
		&lt;soap:Body&gt;
			&lt;MethodCallResponse&gt;
				&lt;Three&gt;three&lt;/Three&gt;
			&lt;/MethodCallResponse&gt;
		&lt;/soap:Body&gt;
	&lt;/soap:Envelope&gt;`))
	find := []xml.Name{
		xml.Name{&quot;&quot;, &quot;MethodCallResponse&quot;},
		xml.Name{&quot;http://www.w3.org/2003/05/soap-envelope&quot;, &quot;Fault&quot;},
	}
	var start *xml.StartElement
	var err error
	if start, err = dec.Find(find); err != nil {
		log.Fatal(err)
	}
	if start.Name.Local == &quot;Fault&quot; {
		log.Fatal(&quot;Fault!&quot;) // Here you can decode a Soap Fault
	}
	var resp MethodCallResponse
	if err := dec.DecodeElement(&amp;resp, start); err != nil {
		log.Fatal(err)
	}
	fmt.Printf(&quot;%#v\n\n&quot;, resp)
}

With the above example I use the Find method to get the response object, or a Fault. This isn't strictly necessary. You can also do it like this:

x.RegisterType(MethodCallResponse{})
...
// Decode response
dec := x.NewDecoder(bytes.NewBufferString(`&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;soap:Envelope&gt;
	&lt;soap:Body&gt;
		&lt;MethodCallResponse&gt;
			&lt;Three&gt;three&lt;/Three&gt;
		&lt;/MethodCallResponse&gt;
	&lt;/soap:Body&gt;
&lt;/soap:Envelope&gt;`))
var start *xml.StartElement
var resp Envelope
if err := dec.DecodeElement(&amp;resp, start); err != nil {
	log.Fatal(err)
}
fmt.Printf(&quot;%#v\n\n&quot;, resp)

You'll find the Find method useful when your data looks like this:

&lt;soap:Envelope&gt;
  &lt;soap:Body&gt;
    &lt;MethodResponse&gt;
      &lt;MethodResult&gt;
        &lt;diffgr:diffgram&gt;
          &lt;NewDataSet&gt;
            &lt;Table1 diffgr:id=&quot;Table1&quot; msdata:rowOrder=&quot;0&quot; diffgr:hasChanges=&quot;inserted&quot;&gt;
              &lt;Three&gt;three&lt;/Three&gt;
            &lt;/Table1&gt;
          &lt;/NewDataSet&gt;
        &lt;/diffgr:diffgram&gt;
      &lt;/MethodResult&gt;
    &lt;/MethodResponse&gt;
  &lt;/soap:Body&gt;
&lt;/soap:Envelope&gt;

This is a DiffGram, part of Microsoft .NET. You can use the Find method to get to Table1. The Decode and DecodeElement method also works on slices. So you can pass in a []MethodCallResponse if NewDataSet happens to contain more than one result.

I do agree with Zippower that SOAP does suck. But unfortunately a lot of enterprises use SOAP, and you're sometimes forced to use these APIs. With the xmlutil package I hope to make it a little less painful to work with.

答案2

得分: 14

不行。

英文:

Nope.

SOAP sucks, but I had to implement a server of an already-defined protocol that uses SOAP, so I listened with net/http and decoded/encoded envelopes with encoding/xml. In few minutes, I already served my first envelope with Go.

答案3

得分: 9

虽然Go语言本身没有提供相关功能,但是有一个叫做gowsdl的工具。到目前为止,它对我来说已经足够好用,可以与多个SOAP服务进行交互。

我没有使用它提供的SOAP代理,因为我相信它不支持身份验证,但是gowsdl可以根据WSDL生成我需要的结构体和代码,用于编组请求和解析响应,这是一个很大的优势。

英文:

While there's still nothing in Go itself, there is gowsdl. So far, it seems to work well enough for me to interface with several SOAP services.

I don't use the SOAP proxy it provides, which I believe doesn't support auth, but gowsdl generates the structs and code I need from the WSDL to marshal requests and unmarshal responses--a big win.

1: https://github.com/hooklift/gowsdl "gowsdl"

答案4

得分: 0

一个选择是使用gsoap,它可以生成一个C WSDL客户端,然后通过cgo在GO中使用该客户端。

英文:

one option is to use gsoap which produces a C WSDL client
and then use that client through GO with cgo

huangapple
  • 本文由 发表于 2012年8月2日 05:10:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/11767642.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定