From 90a8d1e65edd4695376f752b9cd28e28481711ef Mon Sep 17 00:00:00 2001 From: miki Date: Wed, 17 Jan 2024 14:02:15 +0100 Subject: [PATCH 1/3] custom enveloped request --- client.go | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/client.go b/client.go index 0915118..3239302 100644 --- a/client.go +++ b/client.go @@ -244,6 +244,162 @@ func (c *Client) Call(ctx context.Context, soapAction string, request, response return httpResponse, nil } +type CustomEnvelope struct { + Envelope struct{} +} + +// Call makes a SOAP call +func (c *Client) CallWithCustomEnvelope(ctx context.Context, soapAction string, request CustomEnvelope, response interface{}) (*http.Response, error) { + envelope := request + + xmlBytes, err := c.Marshaller.Marshal(envelope) + if err != nil { + return nil, err + } + // Adjust namespaces for SOAP 1.2 + if c.SoapVersion == SoapVersion12 { + xmlBytes = replaceSoap11to12(xmlBytes) + } + + req, err := http.NewRequestWithContext(ctx, "POST", c.url, bytes.NewReader(xmlBytes)) + if err != nil { + return nil, err + } + if c.auth != nil { + req.SetBasicAuth(c.auth.Login, c.auth.Password) + } + + req.Header.Add("Content-Type", c.ContentType) + ua := c.UserAgent + if ua == "" { + ua = userAgent + } + req.Header.Set("User-Agent", ua) + + if soapAction != "" { + req.Header.Add("SOAPAction", soapAction) + } + + req.Close = true + if c.RequestHeaderFn != nil { + c.RequestHeaderFn(req.Header) + } + var logTraceID string + if c.Log != nil { + logTraceID = randString(12) + c.Log("Request", "log_trace_id", logTraceID, "url", c.urlMasked, "request_bytes", string(xmlBytes)) + hdr := req.Header.Clone() + for _, n := range c.LogRemoveHeaderNames { + hdr.Set(n, "removed") + } + c.Log("Header", "log_trace_id", logTraceID, "Header", hdr) + } + httpResponse, err := c.HTTPClientDoFn(req) + if err != nil { + return nil, err + } + defer httpResponse.Body.Close() + + if c.Log != nil { + c.Log("Response header", "log_trace_id", logTraceID, "header", httpResponse.Header) + } + mediaType, params, err := mime.ParseMediaType(httpResponse.Header.Get("Content-Type")) + if err != nil { + if c.Log != nil { + c.Log("WARNING", "log_trace_id", logTraceID, "error", err) + } + } + if c.Log != nil { + c.Log("MIMETYPE", "log_trace_id", logTraceID, "mediaType", mediaType) + } + var rawBody []byte + if strings.HasPrefix(mediaType, "multipart/") { // MULTIPART MESSAGE + mr := multipart.NewReader(httpResponse.Body, params["boundary"]) + // If this is a multipart message, search for the soapy part + foundSoap := false + for { + p, err := mr.NextPart() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + slurp, err := ioutil.ReadAll(p) + if err != nil { + return nil, err + } + if bytes.HasPrefix(slurp, soapPrefixTagLC) || bytes.HasPrefix(slurp, soapPrefixTagUC) { + rawBody = slurp + foundSoap = true + break + } + } + if !foundSoap { + return nil, errors.New("multipart message does contain a soapy part") + } + } else { // SINGLE PART MESSAGE + rawBody, err = ioutil.ReadAll(httpResponse.Body) + if err != nil { + return httpResponse, err // return both + } + // Check if there is a body and if yes if it's a soapy one. + if len(rawBody) == 0 { + if c.Log != nil { + c.Log("INFO: Response Body is empty!", "log_trace_id", logTraceID) + } + return httpResponse, nil // Empty responses are ok. Sometimes Sometimes only a Status 200 or 202 comes back + } + // There is a message body, but it's not SOAP. We cannot handle this! + switch c.SoapVersion { + case SoapVersion12: + if !bytes.Contains(rawBody, []byte(`soap-envelope`)) { // not quite sure if correct to assert on soap-... + if c.Log != nil { + c.Log("This is not a 1.2 SOAP-Message", "log_trace_id", logTraceID, "response_bytes", rawBody) + } + return nil, fmt.Errorf("this is not a 1.2 SOAP-Message: %q", string(rawBody)) + } + default: + if !(bytes.Contains(rawBody, soapPrefixTagLC) || bytes.Contains(rawBody, soapPrefixTagUC)) { + if c.Log != nil { + c.Log("This is not a 1.1 SOAP-Message", "log_trace_id", logTraceID, "response_bytes", rawBody) + } + return nil, fmt.Errorf("this is not a 1.1 SOAP-Message: %q", string(rawBody)) + } + } + } + + // We have an empty body or a SOAP body + if c.Log != nil { + c.Log("response raw body", "log_trace_id", logTraceID, "response_bytes", rawBody) + } + + // Our structs for Envelope, Header, Body and Fault are tagged with namespace + // for SOAP 1.1. Therefore we must adjust namespaces for incoming SOAP 1.2 + // messages + rawBody = replaceSoap12to11(rawBody) + + respEnvelope := &Envelope{ + Body: Body{Content: response}, + } + // Response struct may be nil, e.g. if only a Status 200 is expected. In this + // case, we need a Dummy response to avoid a nil pointer if we receive a + // SOAP-Fault instead of the empty message (unmarshalling would fail). + if response == nil { + respEnvelope.Body = Body{Content: &dummyContent{}} // must be a pointer in dummyContent + } + if err := xml.Unmarshal(rawBody, respEnvelope); err != nil { + return nil, fmt.Errorf("soap/client.go Call(): COULD NOT UNMARSHAL: %w\n", err) + } + + // If a SOAP Fault is received, try to jsonMarshal it and return it via the + // error. + if fault := respEnvelope.Body.Fault; fault != nil { + return nil, fmt.Errorf("SOAP FAULT: %q", formatFaultXML(rawBody, 1)) + } + return httpResponse, nil +} + // Format the Soap Fault as indented string. Namespaces are dropped for better // readability. Tags with lower level than start level is omitted. func formatFaultXML(xmlBytes []byte, startLevel int) string { From ac6dc8361271ba397ab3b09a244632dfa458e6f9 Mon Sep 17 00:00:00 2001 From: miki Date: Wed, 17 Jan 2024 14:03:50 +0100 Subject: [PATCH 2/3] go.mod update --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 735a448..a6a12d3 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/globusdigital/soap +module github.com/antalmiklos/soap go 1.19 From 9785f6ae8ca830bf8d8d322380113d4fe28e16b2 Mon Sep 17 00:00:00 2001 From: miki Date: Wed, 17 Jan 2024 14:05:33 +0100 Subject: [PATCH 3/3] removing examples --- examples/client.go | 34 -------------------------- examples/simple-server.go | 51 --------------------------------------- go.mod | 4 ++- go.sum | 2 ++ 4 files changed, 5 insertions(+), 86 deletions(-) delete mode 100644 examples/client.go delete mode 100644 examples/simple-server.go diff --git a/examples/client.go b/examples/client.go deleted file mode 100644 index 014afda..0000000 --- a/examples/client.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "context" - "encoding/xml" - "log" - - "github.com/globusdigital/soap" -) - -// FooRequest a simple request -type FooRequest struct { - XMLName xml.Name `xml:"fooRequest"` - Foo string -} - -// FooResponse a simple response -type FooResponse struct { - Bar string -} - -func main() { - client := soap.NewClient("http://127.0.0.1:8080/", nil) - client.Log = func(msg string, keyString_ValueInterface ...interface{}) { - keyString_ValueInterface = append(keyString_ValueInterface, msg) - log.Println(keyString_ValueInterface...) - } // verbose - response := &FooResponse{} - httpResponse, err := client.Call(context.Background(), "operationFoo", &FooRequest{Foo: "hello i am foo"}, response) - if err != nil { - panic(err) - } - log.Println(response.Bar, httpResponse.Status) -} diff --git a/examples/simple-server.go b/examples/simple-server.go deleted file mode 100644 index 06a43db..0000000 --- a/examples/simple-server.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "encoding/xml" - "fmt" - "log" - "net/http" - - "github.com/globusdigital/soap" -) - -// FooRequest a simple request -type FooRequest struct { - XMLName xml.Name `xml:"fooRequest"` - Foo string -} - -// FooResponse a simple response -type FooResponse struct { - Bar string -} - -// RunServer run a little demo server -func RunServer() { - soapServer := soap.NewServer() - soapServer.Log = log.Println - soapServer.RegisterHandler( - "/pathTo", - "operationFoo", // SOAPAction - "fooRequest", // tagname of soap body content - // RequestFactoryFunc - give the server sth. to unmarshal the request into - func() interface{} { - return &FooRequest{} - }, - // OperationHandlerFunc - do something - func(request interface{}, w http.ResponseWriter, httpRequest *http.Request) (response interface{}, err error) { - fooRequest := request.(*FooRequest) - fooResponse := &FooResponse{ - Bar: "Hello \"" + fooRequest.Foo + "\"", - } - response = fooResponse - return - }, - ) - err := http.ListenAndServe(":8080", soapServer) - fmt.Println("exiting with error", err) -} - -func main() { - RunServer() -} diff --git a/go.mod b/go.mod index a6a12d3..4c31525 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,9 @@ module github.com/antalmiklos/soap go 1.19 -require github.com/stretchr/testify v1.8.4 +require ( + github.com/stretchr/testify v1.8.4 +) require ( github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index fa4b6e6..11a1f3d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/globusdigital/soap v1.4.0 h1:nQDpWelZr3zKg6Oe1e5SqWd4PfiUvsl5/44oWUXy3II= +github.com/globusdigital/soap v1.4.0/go.mod h1:p8hjOZ4FmK0jXBTcIZ6e5M2QBfcsxBUKWBYsHM2eZRw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=