Существуют ли пакеты для поддержки SOAP/WSDL на Go?
Поддержка WSDL/SOAP на Go?
Ответы (5)
Неа.
SOAP отстой, но мне пришлось реализовать сервер уже определенного протокола, который использует SOAP, поэтому я слушал с net/http
и декодировал/кодировал конверты с encoding/xml
. Через несколько минут я уже отправил свой первый конверт с Go.
В 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"`
Vaue 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>
Это DiffGram, часть Microsoft .NET. Вы можете использовать метод Find
, чтобы добраться до Table1
. Методы Decode
и DecodeElement
также работают со срезами. Таким образом, вы можете передать []MethodCallResponse
, если NewDataSet
содержит более одного результата.
Я согласен с Zippower, что SOAP — отстой. Но, к сожалению, многие предприятия используют SOAP, и вам иногда приходится использовать эти API. С пакетом xmlutil я надеюсь сделать работу с ним немного менее болезненной.
Хотя в самом Go еще ничего нет, есть gowsdl. Пока мне кажется, что он работает достаточно хорошо, чтобы взаимодействовать с несколькими службами SOAP.
Я не использую предоставляемый им прокси-сервер SOAP, который, по моему мнению, не поддерживает аутентификацию, но gowsdl генерирует из WSDL нужные мне структуры и код для маршалинга запросов и демаршалирования ответов — большая победа.
Существует также wsdl-go.
Но я не использовал его, поэтому я действительно не могу сказать.
один из вариантов — использовать gsoap, который создает клиент C WSDL, а затем использовать этот клиент через GO с cgo