go-xml rpc takes another path to provide parsing of xml. Instead of reflect for binding xml => go, go-xmlrpc generates code for xml parsing. It uses etree implementation and generates code directly for service methods. Isn't that nice? It's safe, fast, awesome!
Ok let's have a look at example
//go:generate xmlrpcgen --file $GOFILE HelloService
package example
/*
SearchService xml rpc service for searching in database
*/
type SearchService struct {
Config Config
}
/*
Search service method
*/
func (h *SearchService) Search(query string, page int, isit bool) ([]string, error) {
return []string{}, nil
}
That's pretty simple service with search method.
Now run go generate
and go-xmlrpc parses your service methods and generates xml parsing code directly to your methods.
Current version generates this code:
// This file is autogenerated by xmlrpcgen
// do not change it directly!
package core
import (
"github.com/beevik/etree"
"github.com/phonkee/go-xmlrpc"
"strconv"
)
var (
availableMethodsForSearchService = map[string]bool{
"Search": true,
}
)
/*
MethodExists returns whether rpc method is available on service
*/
func (s *SearchService) MethodExists(method string) (ok bool) {
_, ok = availableMethodsForSearchService[method]
return
}
/*
ListMethods returns list of all available methods for given service
*/
func (s *SearchService) ListMethods() []string {
result := make([]string, 0, len(availableMethodsForSearchService))
for key := range availableMethodsForSearchService {
result = append(result, key)
}
return result
}
/*
Dispatch dispatches method on service, do not use this method directly.
root is params *etree.Element (actually "methodCall/params"
*/
func (s *SearchService) Dispatch(method string, root *etree.Element) (doc *etree.Document, err error) {
// call appropriate methods
switch method {
case "Search":
// Get parameters from xmlrpc request
v_2 := root.FindElement("param[1]/value")
if v_2 == nil {
err = xmlrpc.Errorf(400, "could not find query")
return
}
var query string
if query, err = xmlrpc.XPathValueGetString(v_2, "query"); err != nil {
return
}
v_3 := root.FindElement("param[2]/value")
if v_3 == nil {
err = xmlrpc.Errorf(400, "could not find page")
return
}
var page int
if page, err = xmlrpc.XPathValueGetInt(v_3, "page"); err != nil {
return
}
v_4 := root.FindElement("param[3]/value")
if v_4 == nil {
err = xmlrpc.Errorf(400, "could not find isit")
return
}
var isit bool
if isit, err = xmlrpc.XPathValueGetBool(v_4, "isit"); err != nil {
return
}
// If following method call fails there are 2 possible reasons:
// 1. you have either changed method signature or you deleted method. Please re-run "go generate"
// 2. you have probably found a bug and you should file issue on github.
// @TODO: add panic recovery that returns error with 500 code
var result_1 []string
result_1, err = s.Search(query, page, isit)
// create *etree.Document
doc = etree.NewDocument()
doc.CreateProcInst("xml", "version=\"1.0\" encoding=\"UTF-8\"")
methodResponse_5 := doc.CreateElement("methodResponse")
if err != nil {
// move this code to error.
fault_10 := methodResponse_5.CreateElement("fault")
code_9 := 500
// Try to cast error to xmlrpc.Error (with code added)
if code_6, ok_8 := err.(xmlrpc.Error); ok_8 {
code_9 = code_6.Code()
}
struct_7 := fault_10.CreateElement("value").CreateElement("struct")
member_11 := struct_7.CreateElement("member")
member_11.CreateElement("name").SetText("faultCode")
member_11.CreateElement("value").CreateElement("int").SetText(strconv.Itoa(code_9))
member_12 := struct_7.CreateElement("member")
member_12.CreateElement("name").SetText("faultString")
member_12.CreateElement("value").CreateElement("string").SetText(err.Error())
} else {
// here is place where we need to hydrate results
v_13 := methodResponse_5.CreateElement("params").CreateElement("param").CreateElement("value")
array_data_14 := v_13.CreateElement("array").CreateElement("data")
for _, item_15 := range result_1 {
value_16 := array_data_14.CreateElement("value")
value_16.CreateElement("string").SetText(item_15)
}
}
default:
// method not found, this should not happened since we check whether method exists
err = xmlrpc.ErrMethodNotFound
return
}
return
}
go-xmlrpc has handler api so you can register your service instance (pointer) to handler and directly pass as http.Handler
handler := xmlrpc.Handler()
// We pass Config as example so you see that you can provide database connection or any other resource to service.
if err := handler.AddService(&HelloService{Config:Config}, "hello"); err != nil {
panic(err)
}
You can then call methods hello.Search
with your favorite xmlrpc client.
You can use then handler directly in your favorite mux router since it is Handler.
Your service methods must return either:
- error - simple error or xmlrpc.Error with code (
xmlrpc.Errorf(400, "this %s", "error)
) - result and error
This is because xml rpc should return at least error.
If you return xmlrpc error with added code it will be addedded to result.fault
.
Otherwise error code will be 500.
- since go-xmlrpc generates code in your package, you can use also unexported methods (yay)
- you can register your services with instantiated database connections, or other variables
- Automatically adds
system.listMethods
with all available methods - inspect service method arguments and return values recursively (yay nice!)
- Registered services must be pointers (just to be sure all your methods are usable)
- arguments currently don't support pointers (will be used in the future for non required arguments)
Don't forget to rerun go generate
when you either:
- change definition of service methods
- remove service methods
- add service methods
- Add support for missing types (unsigned integer family)
- Add proper error messages to parse errors (with whole path).
- Cleanup code generation with proper documentation
- Possibly remove temporary variables in parsing code.
Your PRs are welcome
phonkee