mirror of
https://github.com/foomo/soap.git
synced 2025-10-16 12:45:36 +00:00
246 lines
6.5 KiB
Go
246 lines
6.5 KiB
Go
package soap
|
|
|
|
import (
|
|
"encoding/json"
|
|
"encoding/xml"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// OperationHandlerFunc runs the actual business logic - request is whatever you constructed in RequestFactoryFunc
|
|
type OperationHandlerFunc func(request interface{}, w http.ResponseWriter, httpRequest *http.Request) (response interface{}, err error)
|
|
|
|
// RequestFactoryFunc constructs a request object for OperationHandlerFunc
|
|
type RequestFactoryFunc func() interface{}
|
|
|
|
type dummyContent struct{}
|
|
|
|
type operationHander struct {
|
|
requestFactory RequestFactoryFunc
|
|
handler OperationHandlerFunc
|
|
}
|
|
|
|
type responseWriter struct {
|
|
w http.ResponseWriter
|
|
outputStarted bool
|
|
}
|
|
|
|
func (w *responseWriter) Header() http.Header {
|
|
return w.w.Header()
|
|
}
|
|
func (w *responseWriter) Write(b []byte) (int, error) {
|
|
w.outputStarted = true
|
|
if Verbose {
|
|
l("writing response: " + string(b))
|
|
}
|
|
return w.w.Write(b)
|
|
}
|
|
|
|
func (w *responseWriter) WriteHeader(code int) {
|
|
w.w.WriteHeader(code)
|
|
}
|
|
|
|
// Server a SOAP server, which can be run standalone or used as a http.HandlerFunc
|
|
type Server struct {
|
|
handlers map[string]map[string]map[string]*operationHander
|
|
Marshaller XMLMarshaller
|
|
ContentType string
|
|
SoapVersion string
|
|
}
|
|
|
|
// NewServer construct a new SOAP server
|
|
func NewServer() *Server {
|
|
s := &Server{
|
|
handlers: make(map[string]map[string]map[string]*operationHander),
|
|
Marshaller: newDefaultMarshaller(),
|
|
ContentType: SoapContentType11,
|
|
SoapVersion: SoapVersion11,
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (s *Server) UseSoap11() {
|
|
s.SoapVersion = SoapVersion11
|
|
s.ContentType = SoapContentType11
|
|
}
|
|
|
|
func (s *Server) UseSoap12() {
|
|
s.SoapVersion = SoapVersion12
|
|
s.ContentType = SoapContentType12
|
|
}
|
|
|
|
// RegisterHandler register to handle an operation
|
|
func (s *Server) RegisterHandler(path string, action string, messageType string, requestFactory RequestFactoryFunc, operationHandlerFunc OperationHandlerFunc) {
|
|
_, pathHandlersOK := s.handlers[path]
|
|
if !pathHandlersOK {
|
|
s.handlers[path] = make(map[string]map[string]*operationHander)
|
|
}
|
|
_, ok := s.handlers[path][action]
|
|
if !ok {
|
|
s.handlers[path][action] = make(map[string]*operationHander)
|
|
}
|
|
s.handlers[path][action][messageType] = &operationHander{
|
|
handler: operationHandlerFunc,
|
|
requestFactory: requestFactory,
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleError(err error, w http.ResponseWriter) {
|
|
// has to write a soap fault
|
|
l("handling error:", err)
|
|
responseEnvelope := &Envelope{
|
|
Body: Body{
|
|
Content: &Fault{
|
|
String: err.Error(),
|
|
},
|
|
},
|
|
}
|
|
xmlBytes, xmlErr := s.Marshaller.Marshal(responseEnvelope)
|
|
if xmlErr != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte("could not marshal soap fault for: " + err.Error() + " xmlError: " + xmlErr.Error()))
|
|
} else {
|
|
addSOAPHeader(w, len(xmlBytes), s.ContentType)
|
|
w.Write(xmlBytes)
|
|
}
|
|
}
|
|
|
|
// WriteHeader first sets header like content-type and then writes the header
|
|
func (s *Server) WriteHeader(w http.ResponseWriter, code int) {
|
|
setContentType(w, s.ContentType)
|
|
w.WriteHeader(code)
|
|
}
|
|
|
|
func setContentType(w http.ResponseWriter, contentType string) {
|
|
w.Header().Set("Content-Type", contentType)
|
|
}
|
|
|
|
func addSOAPHeader(w http.ResponseWriter, contentLength int, contentType string) {
|
|
setContentType(w, contentType)
|
|
w.Header().Set("Content-Length", fmt.Sprint(contentLength))
|
|
}
|
|
|
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
soapAction := r.Header.Get("SOAPAction")
|
|
l("ServeHTTP method:", r.Method, ", path:", r.URL.Path, ", SOAPAction", "\""+soapAction+"\"")
|
|
// we have a valid request time to call the handler
|
|
w = &responseWriter{
|
|
w: w,
|
|
outputStarted: false,
|
|
}
|
|
switch r.Method {
|
|
case "POST":
|
|
l("incoming POST")
|
|
soapRequestBytes, err := ioutil.ReadAll(r.Body)
|
|
// 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
|
|
if s.SoapVersion == SoapVersion12 {
|
|
tmp := string(soapRequestBytes)
|
|
tmp = strings.Replace(tmp, NamespaceSoap12, NamespaceSoap11, -1)
|
|
soapRequestBytes = []byte(tmp)
|
|
}
|
|
|
|
if err != nil {
|
|
s.handleError(errors.New("could not read POST:: "+err.Error()), w)
|
|
return
|
|
}
|
|
pathHandlers, pathHandlerOK := s.handlers[r.URL.Path]
|
|
if !pathHandlerOK {
|
|
s.handleError(errors.New("unknown path"), w)
|
|
return
|
|
}
|
|
actionHandlers, ok := pathHandlers[soapAction]
|
|
if !ok {
|
|
s.handleError(errors.New("unknown action \""+soapAction+"\""), w)
|
|
return
|
|
}
|
|
|
|
// we need to find out, what is in the body
|
|
probeEnvelope := &Envelope{
|
|
Body: Body{
|
|
Content: &dummyContent{},
|
|
},
|
|
}
|
|
|
|
err = s.Marshaller.Unmarshal(soapRequestBytes, &probeEnvelope)
|
|
if err != nil {
|
|
s.handleError(errors.New("could not probe soap body content:: "+err.Error()), w)
|
|
return
|
|
}
|
|
|
|
t := probeEnvelope.Body.SOAPBodyContentType
|
|
l("found content type", t)
|
|
actionHandler, ok := actionHandlers[t]
|
|
if !ok {
|
|
s.handleError(errors.New("no action handler for content type: \""+t+"\""), w)
|
|
return
|
|
}
|
|
request := actionHandler.requestFactory()
|
|
envelope := &Envelope{
|
|
Header: Header{},
|
|
Body: Body{
|
|
Content: request,
|
|
},
|
|
}
|
|
|
|
err = xml.Unmarshal(soapRequestBytes, &envelope)
|
|
if err != nil {
|
|
s.handleError(errors.New("could not unmarshal request:: "+err.Error()), w)
|
|
return
|
|
}
|
|
l("request", jsonDump(envelope))
|
|
|
|
response, err := actionHandler.handler(request, w, r)
|
|
if err != nil {
|
|
l("action handler threw up")
|
|
s.handleError(err, w)
|
|
return
|
|
}
|
|
l("result", jsonDump(response))
|
|
if !w.(*responseWriter).outputStarted {
|
|
responseEnvelope := &Envelope{
|
|
Body: Body{
|
|
Content: response,
|
|
},
|
|
}
|
|
xmlBytes, err := s.Marshaller.Marshal(responseEnvelope)
|
|
// Adjust namespaces for SOAP 1.2
|
|
if s.SoapVersion == SoapVersion12 {
|
|
tmp := string(xmlBytes)
|
|
tmp = strings.Replace(tmp, NamespaceSoap11, NamespaceSoap12, -1)
|
|
xmlBytes = []byte(tmp)
|
|
}
|
|
if err != nil {
|
|
s.handleError(errors.New("could not marshal response:: "+err.Error()), w)
|
|
}
|
|
addSOAPHeader(w, len(xmlBytes), s.ContentType)
|
|
w.Write(xmlBytes)
|
|
} else {
|
|
l("action handler sent its own output")
|
|
}
|
|
|
|
default:
|
|
// this will be a soap fault !?
|
|
s.handleError(errors.New("this is a soap service - you have to POST soap requests"), w)
|
|
}
|
|
}
|
|
|
|
func jsonDump(v interface{}) string {
|
|
if !Verbose {
|
|
return "not dumping"
|
|
}
|
|
jsonBytes, err := json.MarshalIndent(v, "", " ")
|
|
if err != nil {
|
|
return "error in json dump :: " + err.Error()
|
|
}
|
|
return string(jsonBytes)
|
|
}
|
|
|
|
// ListenAndServe run standalone
|
|
func (s *Server) ListenAndServe(addr string) error {
|
|
return http.ListenAndServe(addr, s)
|
|
}
|