go-xmlrpc
Warning! This project is under heavy development!!
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.
Return values:
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.
Error:
If you return xmlrpc error with added code it will be addedded to result.fault
. Otherwise error code will be 500.
Features:
- 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!)
Limitations:
- 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)
Gotchas:
Don't forget to rerun go generate
when you either:
- change definition of service methods
- remove service methods
- add service methods
TODO:
- 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.
Contributions:
Your PRs are welcome
Author:
phonkee