soap/server.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)
}