今回はgowsdlを使ってSalesforceのSOAP APIを叩いてみます。
gowsdlの使い方
こんな感じでコマンドを叩けばOK
$ gowsdl {URL} // -p で出力先ディレクトリを変更する
ローカルファイルは読み込んでくれないので、Web上に置く必要があります。Salesforceの各種WSDLはWeb上にあるものの、いずれも認証を必要とするので、ダウンロードしたWSDLをS3にPublicなオブジェクトとしてデータをアップロードしたりする必要があります。ただし、Enterprise WSDLはスキーマ情報がそのまま乗るので取扱には注意です。試してませんがローカルにサーバ立てて、そこで配信するのが手軽でセキュアかも。
partner.wsdlでやってみるとこんな感じでエラーが出ます。
$ gowsdl -p partner https://s3-ap-northeast-1.amazonaws.com/{BUCKET}/partner.wsdl
Downloading file https://s3-ap-northeast-1.amazonaws.com/{BUCKET}partner.wsdl
Downloading external schema location
https://s3-ap-northeast-1.amazonaws.com/{BUCKET}/partner.wsdl
expected element type schema but have definitions
gowsdlはシングルバイナリなので、デバッグ仕込んだりすることは出来ませんが、ベースとなっているソースコードを直接いじることで原因を調査することができます。
$ go run $GOPATH/src/github.com/hooklift/gowsdl/cmd/gowsdl/main.go \
https://s3-ap-northeast-1.amazonaws.com/{BUCKET}/partner.wsdl
ということで、ここらへんをコメントアウトして無理矢理動かします(白目)
diff --git a/gowsdl.go b/gowsdl.go
index e136e27..6464a7d 100644
--- a/gowsdl.go
+++ b/gowsdl.go
@@ -176,9 +176,9 @@ func (g *GoWSDL) unmarshal() error {
for _, schema := range g.wsdl.Types.Schemas {
err = g.resolveXSDExternals(schema, parsedURL)
- if err != nil {
- return err
- }
+ // if err != nil {
+ // return err
+ // }
}
return nil
これでとりあえずクライアントは自動生成できます。自動生成されたパッケージを少し修正して動かしてみます。 まずはパッケージ名をmainに変更します。
@@ -1,4 +1,4 @@
-package myservice
+package main
同一階層にmain.goを作成する
package main
import (
"github.com/k0kubun/pp"
)
func main() {
soap := NewSoap("", true, nil)
login := Login{
Username: "xxxxx",
Password: "xxxxx",
}
res, err := soap.Login(login)
if err != nil {
pp.Print(err)
}
pp.Print(res)
}
そのままgo build .すると、こんなエラーが出ます
$ go build .
# _/Users/mtajitsu/myservice
./myservice.go:2766: DescribeGlobalTheme redeclared in this block
previous declaration at ./myservice.go:1230
./myservice.go:3344: DescribeApprovalLayout redeclared in this block
previous declaration at ./myservice.go:1374
./myservice.go:3366: DescribeLayout redeclared in this block
previous declaration at ./myservice.go:1316
./myservice.go:4516: undefined: QName
リクエスト用の構造体とレスポンスの構造体の名前が重複しているのが原因なので、リクエスト用の構造体はXXXRequestとするように名前を変更します。
またQNameはstringと置き換えても問題なさそうなので、置き換えます。
@@ -1227,7 +1227,7 @@
Result *DescribeGlobalResult `xml:"result,omitempty"`
}
-type DescribeGlobalTheme struct {
+type DescribeGlobalThemeRequest struct {
XMLName xml.Name `xml:"urn:partner.soap.sforce.com describeGlobalTheme"`
}
@@ -1313,7 +1313,7 @@
Result *DescribeAppMenuResult `xml:"result,omitempty"`
}
-type DescribeLayout struct {
+type DescribeLayoutRequest struct {
XMLName xml.Name `xml:"urn:partner.soap.sforce.com describeLayout"`
SObjectType string `xml:"sObjectType,omitempty"`
@@ -1371,7 +1371,7 @@
Result *DescribePathAssistantsResult `xml:"result,omitempty"`
}
-type DescribeApprovalLayout struct {
+type DescribeApprovalLayoutRequest struct {
XMLName xml.Name `xml:"urn:partner.soap.sforce.com describeApprovalLayout"`
SObjectType string `xml:"sObjectType,omitempty"`
@@ -4513,7 +4514,7 @@
ExceptionCodeXMLPARSERERROR ExceptionCode = "XMLPARSERERROR"
)
-type FaultCode *QName
+type FaultCode string
const (
FaultCodeFnsAPEXTRIGGERCOUPLINGLIMIT FaultCode = "fnsAPEXTRIGGERCOUPLINGLIMIT"
@@ -5065,7 +5066,7 @@
//
// - UnexpectedErrorFault
/* Describe Gloal and Themes */
-func (service *Soap) DescribeGlobalTheme(request *DescribeGlobalTheme) (*DescribeGlobalThemeResponse, error) {
+func (service *Soap) DescribeGlobalTheme(request *DescribeGlobalThemeRequest) (*DescribeGlobalThemeResponse, error) {
response := new(DescribeGlobalThemeResponse)
err := service.client.Call("", request, response)
if err != nil {
@@ -5095,7 +5096,7 @@
// - UnexpectedErrorFault
// - InvalidIdFault
/* Describe the layout of the given sObject or the given actionable global page. */
-func (service *Soap) DescribeLayout(request *DescribeLayout) (*DescribeLayoutResponse, error) {
+func (service *Soap) DescribeLayout(request *DescribeLayoutRequest) (*DescribeLayoutResponse, error) {
response := new(DescribeLayoutResponse)
err := service.client.Call("", request, response)
if err != nil {
@@ -5179,7 +5180,7 @@
}
/* Describe the approval layouts of the given sObject */
-func (service *Soap) DescribeApprovalLayout(request *DescribeApprovalLayout) (*DescribeApprovalLayoutResponse, error) {
+func (service *Soap) DescribeApprovalLayout(request *DescribeApprovalLayoutRequest) (*DescribeApprovalLayoutResponse, error) {
response := new(DescribeApprovalLayoutResponse)
err := service.client.Call("", request, response)
if err != nil {
ここで go buildしてもまだエラーが出ます。
errors.errorString{
s: "xml: name \"result\" in tag of main.LoginResponse.Result conflicts with name \"LoginResult\" in *main.LoginResult.XMLName",
}(*main.LoginResponse)(nil)
タグ名が一致していない、というエラーです。gowsdlのコメントアウトに起因している可能性もありますが、修正不要なwsdlでやっても同じエラーが出たのでそういう仕様なのかもしれません。とりあえずピンポイントにタグ名を一致させるか、子のタグ名表記を消します。
@@ -2214,7 +2214,8 @@
}
type GetUserInfoResult struct {
- XMLName xml.Name `xml:"urn:partner.soap.sforce.com GetUserInfoResult"`
+ // resultとuserInfoの両パターンあるためコメントアウト
+ // XMLName xml.Name `xml:"urn:partner.soap.sforce.com GetUserInfoResult"`
AccessibilityMode bool `xml:"accessibilityMode,omitempty"`
@@ -2264,7 +2265,7 @@
}
type LoginResult struct {
- XMLName xml.Name `xml:"urn:partner.soap.sforce.com LoginResult"`
+ XMLName xml.Name `xml:"urn:partner.soap.sforce.com result"`
MetadataServerUrl string `xml:"metadataServerUrl,omitempty"`
そうするとgo buildは通りますが、SOAP RequestのFaultが発生します。
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<soapenv:Fault>
<faultcode>soapenv:Client</faultcode>
<faultstring>SOAPAction HTTP header missing</faultstring>
</soapenv:Fault>
</soapenv:Body>
</soapenv:Envelope>
SalesforceのSOAP APIはSOAPActionに空文字でも良いので入れる必要があるので無理矢理入れます。
@@ -5883,10 +5884,8 @@
}
req.Header.Add("Content-Type", "text/xml; charset=\"utf-8\"")
- if soapAction != "" {
- req.Header.Add("SOAPAction", soapAction)
- }
-
+ req.Header.Add("SOAPAction", "''")
+
req.Header.Set("User-Agent", "gowsdl/0.1")
req.Close = true
これでログインが通るのですが、SOAPのログ出力がうざいので取ります。
@@ -5872,7 +5873,7 @@
return err
}
- log.Println(buffer.String())
+ // log.Println(buffer.String())
req, err := http.NewRequest("POST", s.url, buffer)
if err != nil {
@@ -5913,7 +5912,7 @@
return nil
}
- log.Println(string(rawbody))
+ // log.Println(string(rawbody))
respEnvelope := new(SOAPEnvelope)
respEnvelope.Body = SOAPBody{Content: response}
err = xml.Unmarshal(rawbody, respEnvelope)
ということでgowsdlの修正と生成されたクライアント側の修正がかなり多いです…。とはいえ、ここまでやってしまえばタグ名の不一致以外のエラーは発生しないので安心してSOAP APIをコールすることができます。 Metadata APIの場合 Metadata WSDLの場合は修正不要でgowsdlが利用できますが、Partner WSDLと違って以下の点を修正する必要があります。
リクエスト用のDeploy構造体のZipFileメンバの型名の変更
@@ -4382,7 +4382,7 @@
type Deploy struct {
XMLName xml.Name `xml:"http://soap.sforce.com/2006/04/metadata deploy"`
- ZipFile []byte `xml:"ZipFile,omitempty"`
+ ZipFile string `xml:"ZipFile,omitempty"`
DeployOptions *DeployOptions `xml:"DeployOptions,omitempty"`
}
Loginコールが無いので追記する
@@ -12921,10 +12922,35 @@
Level *LogCategoryLevel `xml:"level,omitempty"`
}
+type LoginRequest struct {
+ XMLName xml.Name `xml:"urn:partner.soap.sforce.com login"`
+ Username string `xml:"username"`
+ Password string `xml:"password"`
+}
+
+type LoginResponse struct {
+ XMLName xml.Name `xml:"urn:partner.soap.sforce.com loginResponse"`
+ LoginResult LoginResult `xml:"result"`
+}
+
+type LoginResult struct {
+ MetadataServerUrl string `xml:"metadataServerUrl"`
+ PasswordExpired bool `xml:"passwordExpired"`
+ Sandbox bool `xml:"sandbox`
+ ServerUrl string `xml:"serverUrl"`
+ SessionId string `xml:"sessionId"`
+ UserId *ID `xml:"userId"`
+ // UserInfo *UserInfo `xml:"userInfo"` //UserInfo構造体は面倒なので一旦コメントアウト
+}
+
type MetadataPortType struct {
client *SOAPClient
}
+func (service *MetadataPortType) SetServerUrl(url string) {
+ service.client.SetServerUrl(url)
+}
+
@@ -13105,6 +13132,17 @@
return response, nil
}
+/* Upserts metadata entries synchronously. */
+func (service *MetadataPortType) Login(request *LoginRequest) (*LoginResponse, error) {
+ response := new(LoginResponse)
+ err := service.client.Call("''", request, response)
+ if err != nil {
+ return nil, err
+ }
+
+ return response, nil
+}
+
@@ -13217,6 +13255,10 @@
s.header = header
}
+func (s *SOAPClient) SetServerUrl(url string) {
+ s.url = url
+}
+
あとはこんな感じで使えばOK
portType := NewMetadataPortType("https://"+endpoint+"/services/Soap/u/"+apiversion, true, nil)
loginRequest := LoginRequest{Username: username, Password: password}
loginResponse, err := portType.Login(loginRequest)
if err != nil {
return err
}
loginResult := loginResponse.LoginResult
request := Deploy{
ZipFile: base64.StdEncoding.EncodeToString(buf),
DeployOptions: nil,
}
sessionHeader := SessionHeader{
SessionId: loginResult.SessionId,
}
portType.SetHeader(sessionHeader)
portType.SetServerUrl(loginResult.MetadataServerUrl)
portType.Deploy(request)