From 577aa766959c75666a5c7c3e3f2a37af7ba68b41 Mon Sep 17 00:00:00 2001 From: John Beisley Date: Tue, 14 Sep 2021 20:35:45 +0100 Subject: [PATCH] First draft of v2/soap/envelope package. --- v2/soap/envelope/envelope.go | 129 ++++++++++++++++++++++++++++++ v2/soap/envelope/envelope_test.go | 40 +++++++++ 2 files changed, 169 insertions(+) create mode 100644 v2/soap/envelope/envelope.go create mode 100644 v2/soap/envelope/envelope_test.go diff --git a/v2/soap/envelope/envelope.go b/v2/soap/envelope/envelope.go new file mode 100644 index 0000000..a4da15c --- /dev/null +++ b/v2/soap/envelope/envelope.go @@ -0,0 +1,129 @@ +// Package envelope is responsible for encoding and decoding SOAP envelopes. +package envelope + +import ( + "encoding/xml" + "fmt" + "io" +) + +// Fault implements error, and contains SOAP fault information. +type Fault struct { + Code string `xml:"faultcode"` + String string `xml:"faultstring"` + Actor string `xml:"faultactor"` + Detail struct { + Raw []byte `xml:",innerxml"` + } `xml:"detail"` +} + +func (fe *Fault) Error() string { + return fmt.Sprintf("SOAP fault code=%s: %s", fe.Code, fe.String) +} + +// Various "constant" bytes used in the written envelope. +var ( + envOpen = []byte(xml.Header + ``) + env1 = []byte(``) + env4 = []byte(``) + envClose = []byte(``) +) + +// Write marshals a SOAP envelope to the writer. Errors can be from the writer +// or XML encoding. +func Write(w io.Writer, action *Action) error { + // Experiments with one router have shown that it 500s for requests where + // the outer default xmlns is set to the SOAP namespace, and then + // reassigning the default namespace within that to the service namespace. + // Most of the code in this function is hand-coding the outer XML to work + // around this. + + _, err := w.Write(envOpen) + if err != nil { + return err + } + _, err = w.Write(env1) + if err != nil { + return err + } + err = xml.EscapeText(w, []byte(action.XMLName.Local)) + if err != nil { + return err + } + _, err = w.Write(env2) + if err != nil { + return err + } + err = xml.EscapeText(w, []byte(action.XMLName.Space)) + if err != nil { + return err + } + _, err = w.Write(env3) + if err != nil { + return err + } + enc := xml.NewEncoder(w) + err = enc.Encode(action.Args) + if err != nil { + return err + } + err = enc.Flush() + if err != nil { + return err + } + _, err = w.Write(env4) + if err != nil { + return err + } + xml.EscapeText(w, []byte(action.XMLName.Local)) + if err != nil { + return err + } + _, err = w.Write(env5) + if err != nil { + return err + } + _, err = w.Write(envClose) + return err +} + +// Read unmarshals a SOAP envelope from the reader. Errors can either be from +// the reader, XML decoding, or a *Fault. +func Read(r io.Reader, action *Action) error { + env := envelope{ + Body: body{ + Action: action, + }, + } + + dec := xml.NewDecoder(r) + err := dec.Decode(&env) + if err != nil { + return err + } + + if env.Body.Fault != nil { + return env.Body.Fault + } + + return nil +} + +type envelope struct { + XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` + EncodingStyle string `xml:"http://schemas.xmlsoap.org/soap/envelope/ encodingStyle,attr"` + Body body `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"` +} + +type body struct { + Fault *Fault `xml:"Fault"` + Action *Action `xml:",any"` +} + +type Action struct { + XMLName xml.Name + Args interface{} `xml:",any"` +} diff --git a/v2/soap/envelope/envelope_test.go b/v2/soap/envelope/envelope_test.go new file mode 100644 index 0000000..1dbed22 --- /dev/null +++ b/v2/soap/envelope/envelope_test.go @@ -0,0 +1,40 @@ +package envelope + +import ( + "bytes" + "encoding/xml" + "reflect" + "testing" +) + +func TestWriteRead(t *testing.T) { + type Args struct { + Foo string `xml:"foo"` + Bar string `xml:"bar"` + } + + sendAction := &Action{ + XMLName: xml.Name{Space: "http://example.com/namespace", Local: "MyAction"}, + Args: &Args{ + Foo: "foo-1", + Bar: "bar-2", + }, + } + + buf := &bytes.Buffer{} + err := Write(buf, sendAction) + if err != nil { + t.Errorf("EncodeEnvelope want success, got err=%v", err) + } + + recvAction := &Action{Args: &Args{}} + + err = Read(buf, recvAction) + if err != nil { + t.Errorf("Reading envelope want success, got err=%v", err) + } + + if !reflect.DeepEqual(sendAction, recvAction) { + t.Errorf("want recvAction=%+v, got %+v", sendAction, recvAction) + } +}