Skip to content

phonkee/go-xmlrpc

Repository files navigation

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

About

Golang support for xml rpc using go generate (generate static parser)

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages