英文:
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
方法获取到Table1
。Decode
和DecodeElement
方法也适用于切片。因此,如果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="xsd:string"
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:"xsi:type,attr"`
Vaue string `xml:",chardata"`
}
And you construct it like this:
MethodCall{
XSI{"xsd:string", "One"},
XSI{"xsd:string", "Two"},
}
Which gives you:
<MethodCall>
<One xsi:type="xsd:string">One</One>
<Two xsi:type="xsd:string">Two</Two>
</MethodCall>
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 (
"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)
}
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(`<?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)
You'll find the Find
method useful when your data looks like this:
<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>
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论