Golang实现SOAP客户端的经验总结

3,443 阅读4分钟

引言

SOAP是一种“简单”的基于XML的协议,在10多年前流行过一阵。与现在行其道的基于JSON协议的restFul API相比,SOAP无疑要笨重许多。

如果项目的开发语言是.net或者Java,那还好还,因为IDE有方便的内置功能来解析SOAP服务的WSDL文件,自动生成Client的相关代码。但如果是用Golang,那就麻烦了。

下面说说SOAP的不同之处,或者说麻烦的地方。

SOAP的不同之处

  • Request/Response结构复杂,嵌套层级多且深

下面是一个Request Body的示例,这个XML是必须指定特定的namespace的。API的验证密钥信息,也是要写在Request Body中。

<soapenv:Envelope xmlns:ns="http://www.id3global.com/ID3gWS/2013/04" xmlns:soap="soap" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
      <wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
         <wsse:UsernameToken>
            <wsse:Username></wsse:Username>
            <wsse:Password></wsse:Password>
         </wsse:UsernameToken>
      </wsse:Security>
   </soapenv:Header>
   <soapenv:Body>
   </soapenv:Body>
</soapenv:Envelope>

通常soapenv:Body部分通常是会更加复杂的,如果没有工具支持,纯手工来写,累人不说,还容易出错。

  • Header的设置
Content-Type: text/xml; charset="utf-8"
SOAPAction: http://www.id3global.com/ID3gWS/2013/04/IGlobalAuthenticate/AuthenticateSP

header中的Content-Typeapplication/xml, 可不要下意识的写成application/json哦。而SOAPAction这个header就是特别注意了,需要设置成要调用的action,而且需要注意的是,不同的action的baseUrl可能是不一样的。相比而言,restFul的API可没这么多坑。

  • multiple part的reponse

虽说大部分时候,SOAP的返回数据是XML格式,但有时会是如下这种multiple part的格式,解析起来也是需要些功夫的。

--uuid:6d335978-5c93-40b7-b5c4-3acc067e3d8c+id=302
Content-ID: <http://tempuri.org/0>
Content-Transfer-Encoding: 8bit
Content-Type: application/xop+xml;charset=utf-8;type="text/xml"

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<s:Header><o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"></s:Header>
<s:Body></s:Body>
</s:Envelope>
--uuid:6d335978-5c93-40b7-b5c4-3acc067e3d8c+id=302--
  • 时间的反序列化与Golang的time.Time类型

Golang的time.Time类型在unmarshal时是需要time zone的信息的,简单来说就是结尾至少要有个Z,例:2021-09-23T14:55:43.835Z。如果没有time zone信息,如:2024-07-13T00:00:00,那么,或者你不用time.Time而用string,再或者就是重写UnmarshalXML的方法了。

解决方案

写了这么多,你也会发现,如果没有现成的工具,自己要处理的细节不是一般的多。

本着技术领域没有新问题,或者说你永远不会是第一个遇到这个问题的人,我开始搜索之路,不得不说搜索还是要google!

我通过medium上的一篇文章,找到心仪的工具, gowsdl,你可以用这个工具方便的生成模板代码:

gowsdl [options] myservice.wsdl

但是呢,我不建议你直接使用生成的代码,而只是使用它定义的结构,自己再重新封装一个client,原因是:

  1. 通常一个wsdl里定义的action特别多,而你只需要用其中一两个action
  2. 再则,生成的代码关于时间字段默认使用的还是time.Time,建议还是自己手动改成gowsdl中定义好的XSDDateTime。想想也是好笑,为啥不直接用XSDDateTime呢。

总体来说,生成的代码也是有很多优点的:

  1. 可以识别出Enum,会生成特定的类型及值。
  2. 可以识别出每个action对应的SOAPAction的http header

最后,再提一点,不知道是我集成的第三方服务的SOAP协议特别古老了,还是这个库有bug,总之,是在处理multiple part的response时,有一些检查的逻辑没过,我是提了一个PR,虽说目前还没人处理,但确实有些tricky。

在这种情况,就涉及到另一个问题,在用go mod管理第三方包的时候,如何hotfix在引用包中发现的问题?

其实,主要是可以分3步:

  1. 在github上,fork一个自己的repo
  2. 改动相关代码,然后Push一个新的tag
  3. 但因为这个Module的引用名称还要保持原来的名字,如果用自己克隆的repo的,就会遇到下面这个错误
go: github.com/ksloveyuan/gowsdl@v0.5.1: parsing go.mod:
	module declares its path as: github.com/hooklift/gowsdl
	        but was required as: github.com/ksloveyuan/gowsdl

我没深入调查原因,但感觉是和go.mod有关。还是那句话,你不会是第一个遇到这个问题人,解决方法是使用go.mod中的replace功能,在go.mod文件的最后,加入下面这行就可以修复这个错误了

replace github.com/hooklift/gowsdl v0.5.0 => github.com/ksloveyuan/gowsdl v0.5.1