From 7dbb05bd743cf9b2255e963e09757e93541f9f88 Mon Sep 17 00:00:00 2001 From: Jan Halfar Date: Sun, 10 Aug 2014 20:07:05 +0200 Subject: [PATCH] initial package layout, first working parts --- .gitignore | 3 + README.md | 35 +++++++++++ foomo/core/client.go | 41 +++++++++++++ foomo/core/client_test.go | 61 ++++++++++++++++++ foomo/foomo.go | 100 ++++++++++++++++++++++++++++++ foomo/foomo_test.go | 47 ++++++++++++++ foomo/token.go | 30 +++++++++ gofoomo.go | 13 ++++ proxy/handler/handler.go | 2 + proxy/handler/rpc.go | 116 +++++++++++++++++++++++++++++++++++ proxy/handler/rpc_test.go | 79 ++++++++++++++++++++++++ proxy/handler/staticfiles.go | 87 ++++++++++++++++++++++++++ proxy/proxy.go | 41 +++++++++++++ rpc/rpc.go | 1 + rpc/value_objects.go | 30 +++++++++ 15 files changed, 686 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 foomo/core/client.go create mode 100644 foomo/core/client_test.go create mode 100644 foomo/foomo.go create mode 100644 foomo/foomo_test.go create mode 100644 foomo/token.go create mode 100644 gofoomo.go create mode 100644 proxy/handler/handler.go create mode 100644 proxy/handler/rpc.go create mode 100644 proxy/handler/rpc_test.go create mode 100644 proxy/handler/staticfiles.go create mode 100644 proxy/proxy.go create mode 100644 rpc/rpc.go create mode 100644 rpc/value_objects.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..01c3a9a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.* +!.git* + diff --git a/README.md b/README.md new file mode 100644 index 0000000..4c48bcd --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# gofoomo + +Gofoomo lets you use Go in your foomo project. It also lets you use php in your Go project. + +We want to use Go, but it is not the right language for a everyone, who is using php. Php´s save and reload work cycle in combination with it´s dynamic character is a great fit especially when doing frontend work. + +## Complementing your LAMP stack + +Go is a much younger and cleaner stack than LAMP. + + - Serve static files without bugging your prefork apache + - Keep slow connections away from your php processes (not implemented yet) + - Hijack foomo json rpc services methods + - Your code is also running the server, this puts you in a place, where you can solve problems, that you can not solve in php + - Go´s runtime model is pretty much the opposite of the php runtime model + - all requests vs one request per lifetime + - shared memory vs process and memory isolation + - one bug to kill them all vs one bug kills one request + - hard, but fast vs easy but slow + +## Sitting in front of your foomo LAMP app with Go + +Go or php? It is up to you, to decide which tool provides better solutions for your problem and who on your team will be more productive with php or Go. + +## Hijacking json rpc calls + +Gofoomo lets you intercept and implement calls to foomo json rpc services. In addition Foomo.Go gives you an infrastructure to generate golang structs for php value objects. + +## Access foomo configurations + +Gofoomo gives you access to foomo configurations from Go. Hint: if your php configuration objects are well annotated they are essentially value objects and corresponding structs can easily be generated with Foomo.Go. + +## More to come, but not much more + +We are going to add features, as we are going to need them. The focus is to have a simple interface between foomo and Go. \ No newline at end of file diff --git a/foomo/core/client.go b/foomo/core/client.go new file mode 100644 index 0000000..950b38f --- /dev/null +++ b/foomo/core/client.go @@ -0,0 +1,41 @@ +package core + +import ( + "encoding/json" + "github.com/foomo/gofoomo/foomo" + "io/ioutil" + "net/http" + "net/url" +) + +func get(foomo *foomo.Foomo, path ...string) (data []byte, err error) { + callUrl := foomo.GetURLWithCredentialsForDefaultBasicAuthDomain() + encodedPath := "" + for _, pathEntry := range path { + encodedPath += "/" + url.QueryEscape(pathEntry) + } + resp, err := http.Get(callUrl + "/foomo/core.php" + encodedPath) + if err == nil { + // handle error + defer resp.Body.Close() + data, err = ioutil.ReadAll(resp.Body) + } + return data, err +} + +func GetJSON(foomo *foomo.Foomo, target interface{}, path ...string) error { + data, err := get(foomo, path...) + if err == nil { + return json.Unmarshal(data, &target) + } else { + return err + } +} + +func GetConfig(foomo *foomo.Foomo, target interface{}, moduleName string, configName string, domain string) (err error) { + if len(domain) == 0 { + return GetJSON(foomo, target, "config", moduleName, configName) + } else { + return GetJSON(foomo, target, "config", moduleName, configName, domain) + } +} diff --git a/foomo/core/client_test.go b/foomo/core/client_test.go new file mode 100644 index 0000000..eeb64d5 --- /dev/null +++ b/foomo/core/client_test.go @@ -0,0 +1,61 @@ +package core + +import ( + "encoding/json" + "github.com/foomo/gofoomo/foomo" + "testing" +) + +type CoreConfig struct { + EnabledModules []string + AvailableModules []string + RootHttp string + buildNumber int64 +} + +var testFoomo *foomo.Foomo + +func getTestFoomo() *foomo.Foomo { + if testFoomo == nil { + f, _ := foomo.NewFoomo("/Users/jan/vagrant/schild/www/schild", "test", "http://schild-local-test.bestbytes.net") + testFoomo = f + } + return testFoomo +} + +func TestGet(t *testing.T) { + f := getTestFoomo() + data, err := get(f, "config", "Foomo", "Foomo.core") + if err != nil { + t.Fatal(err) + } + var jsonData interface{} + err = json.Unmarshal(data, &jsonData) + if err != nil { + t.Fatal(err) + } +} + +func TestGetJSON(t *testing.T) { + f := getTestFoomo() + config := new(CoreConfig) + err := GetJSON(f, config, "config", "Foomo", "Foomo.core") + if err != nil { + t.Fatal(err) + } + if len(config.EnabledModules) < 1 { + t.Fatal("there must be at least Foomo enabled") + } +} + +func TestGetConfig(t *testing.T) { + f := getTestFoomo() + config := new(CoreConfig) + err := GetConfig(f, config, "Foomo", "Foomo.core", "") + if err != nil { + t.Fatal(err) + } + if len(config.EnabledModules) < 1 { + t.Fatal("there must be at least Foomo enabled") + } +} diff --git a/foomo/foomo.go b/foomo/foomo.go new file mode 100644 index 0000000..c41502a --- /dev/null +++ b/foomo/foomo.go @@ -0,0 +1,100 @@ +package foomo + +import ( + "crypto/sha1" + "encoding/base64" + "io/ioutil" + u "net/url" + "strings" +) + +type Foomo struct { + Root string + RunMode string + URL *u.URL + basicAuthCredentials struct { + user string + password string + } +} + +func NewFoomo(foomoDir string, runMode string, url string) (f *Foomo, err error) { + f, err = makeFoomo(foomoDir, runMode, url, true) + return +} + +func makeFoomo(foomoDir string, runMode string, url string, init bool) (foomo *Foomo, err error) { + f := new(Foomo) + f.Root = foomoDir + f.URL, err = u.Parse(url) + f.RunMode = runMode + if init { + f.setupBasicAuthCredentials() + } + return f, err +} + +func (f *Foomo) getBasicAuthFileContentsForDomain(domain string) string { + basicAuthFilename := f.GetBasicAuthFilename("default") + bytes, err := ioutil.ReadFile(basicAuthFilename) + if err != nil { + return "" + } else { + return string(bytes) + } +} + +func (f *Foomo) setupBasicAuthCredentials() error { + f.basicAuthCredentials.user = "gofoomo" + f.basicAuthCredentials.password = makeToken(50) + return ioutil.WriteFile(f.GetBasicAuthFilename("default"), []byte(setBasicAuthForUserInBasicAuthFileContents(f.getBasicAuthFileContentsForDomain("default"), f.basicAuthCredentials.user, f.basicAuthCredentials.password)), 0644) +} + +func setBasicAuthForUserInBasicAuthFileContents(basicAuthFileContents string, user string, password string) string { + newLines := make([]string, 0) +LineLoop: + for _, line := range strings.Split(basicAuthFileContents, "\n") { + lineParts := strings.Split(line, ":") + if len(lineParts) == 2 && lineParts[0] == user { + continue LineLoop + } else { + newLines = append(newLines, line) + } + } + + s := sha1.New() + s.Write([]byte(password)) + passwordSum := []byte(s.Sum(nil)) + newLines = append(newLines, user+":{SHA}"+base64.StdEncoding.EncodeToString(passwordSum)) + return strings.Join(newLines, "\n") +} + +func (f *Foomo) GetURLWithCredentialsForDefaultBasicAuthDomain() string { + url, _ := u.Parse(f.URL.String()) + url.User = u.UserPassword(f.basicAuthCredentials.user, f.basicAuthCredentials.password) + return url.String() +} + +func (f *Foomo) GetBasicAuthCredentialsForDefaultBasicAuthDomain() (user string, password string) { + return f.basicAuthCredentials.user, f.basicAuthCredentials.password +} + +func (f *Foomo) GetModuleDir(moduleName string, dir string) string { + return f.Root + "/modules/" + moduleName + "/" + dir +} + +func (f *Foomo) GetVarDir() string { + return f.Root + "/var/" + f.RunMode +} + +func (f *Foomo) GetModuleHtdocsDir(moduleName string) string { + return f.GetModuleDir(moduleName, "htdocs") +} + +func (f *Foomo) GetModuleHtdocsVarDir(moduleName string) string { + return f.GetVarDir() + "/htdocs/modulesVar/" + moduleName +} + +func (f *Foomo) GetBasicAuthFilename(domain string) string { + return f.GetVarDir() + "/basicAuth/" + domain +} diff --git a/foomo/foomo_test.go b/foomo/foomo_test.go new file mode 100644 index 0000000..0909838 --- /dev/null +++ b/foomo/foomo_test.go @@ -0,0 +1,47 @@ +package foomo + +import ( + "strings" + "testing" +) + +func TestSetBasicAuthForUserInBasicAuthFileContents(t *testing.T) { + ba := "foo:bar\ntest:gone\nhansi:toll" + newBa := setBasicAuthForUserInBasicAuthFileContents(ba, "test", "test") + if len(strings.Split(newBa, "\n")) != 3 { + t.Fatal("wrong line count") + } +} + +func getTestFoomoForFSStuff() *Foomo { + f, _ := makeFoomo("/var/www/foomo", "test", "http://test.foomo", false) + return f +} + +func assertStringsEqual(t *testing.T, topic string, expected string, actual string) { + if actual != expected { + t.Fatal(topic, "actual: ", actual, " != expected: ", expected) + } +} + +func TestGetVarDir(t *testing.T) { + actual := getTestFoomoForFSStuff().GetVarDir() + expected := "/var/www/foomo/var/test" + assertStringsEqual(t, "var dir", expected, actual) +} + +func TestGetModuleDir(t *testing.T) { + assertStringsEqual(t, "module dir", "/var/www/foomo/modules/Foomo/htdocs", getTestFoomoForFSStuff().GetModuleDir("Foomo", "htdocs")) +} + +func TestGetModuleHtdocsDir(t *testing.T) { + assertStringsEqual(t, "module htdocs dir", "/var/www/foomo/modules/Foomo/htdocs", getTestFoomoForFSStuff().GetModuleHtdocsDir("Foomo")) +} + +func TestGetModuleHtdocsVarDir(t *testing.T) { + assertStringsEqual(t, "module htdocs var dir", "/var/www/foomo/var/test/htdocs/modulesVar/Foomo", getTestFoomoForFSStuff().GetModuleHtdocsVarDir("Foomo")) +} + +func TestGetBasicAuthFilename(t *testing.T) { + assertStringsEqual(t, "basic auth file", "/var/www/foomo/var/test/basicAuth/sepp", getTestFoomoForFSStuff().GetBasicAuthFilename("sepp")) +} diff --git a/foomo/token.go b/foomo/token.go new file mode 100644 index 0000000..3d214b0 --- /dev/null +++ b/foomo/token.go @@ -0,0 +1,30 @@ +package foomo + +import ( + "crypto/rand" + "io" +) + +func makeToken(length int) string { + chars := []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#$&*-_") + token := make([]byte, length) + randomData := make([]byte, length+(length/4)) // storage for random bytes. + clen := byte(len(chars)) + maxrb := byte(256 - (256 % len(chars))) + i := 0 + for { + if _, err := io.ReadFull(rand.Reader, randomData); err != nil { + panic(err) + } + for _, c := range randomData { + if c >= maxrb { + continue + } + token[i] = chars[c%clen] + i++ + if i == length { + return string(token) + } + } + } +} diff --git a/gofoomo.go b/gofoomo.go new file mode 100644 index 0000000..7c6ebe6 --- /dev/null +++ b/gofoomo.go @@ -0,0 +1,13 @@ +// Integrate golang with the foomo php framework. Think of gophers riding elephants or +// maybe also think of gophers pulling toy elephants. +// +// +// Example: +// +// f := gofoomo.NewFoomo("/Users/jan/vagrant/schild/www/schild", "test") +// p := proxy.NewProxy(f, "http://schild-local-test.bestbytes.net") +// // the static files handler will keep requests to static files away from apache +// p.AddHandler(handler.NewStaticFiles(f)) +// http.ListenAndServe(":8080", p) +// +package gofoomo diff --git a/proxy/handler/handler.go b/proxy/handler/handler.go new file mode 100644 index 0000000..db50e14 --- /dev/null +++ b/proxy/handler/handler.go @@ -0,0 +1,2 @@ +// Common handlers for the gofoomo.proxy. +package handler diff --git a/proxy/handler/rpc.go b/proxy/handler/rpc.go new file mode 100644 index 0000000..52f4608 --- /dev/null +++ b/proxy/handler/rpc.go @@ -0,0 +1,116 @@ +package handler + +import ( + "encoding/json" + "github.com/foomo/gofoomo/rpc" + "io/ioutil" + "net/http" + "net/url" + "reflect" + "strings" +) + +// This handler helps you to hijack foomo rpc services. Actually it is even +// better, you can hijack them method by method. +// +// f := gofoomo.NewFoomo("/var/www/myApp", "test") +// p := proxy.NewProxy(f, "http://test.myapp") +// service := NewFooService() +// rpcHandler := handler.NewRPC(service, "/foomo/modules/MyModule/services/foo.php") +// f.AddHandler(rpcHandler) +// +// Happy service hijacking! +type RPC struct { + path string + serviceObject interface{} +} + +func NewRPC(serviceObject interface{}, path string) *RPC { + rpc := new(RPC) + rpc.path = path + rpc.serviceObject = serviceObject + return rpc +} + +func (r *RPC) getApplicationPath(path string) string { + return path[len(r.path+"/Foomo.Services.RPC/serve")+1:] +} + +func (r *RPC) getMethodFromPath(path string) string { + parts := strings.Split(r.getApplicationPath(path), "/") + if len(parts) > 0 { + return strings.ToUpper(parts[0][0:1]) + parts[0][1:] + } else { + return "" + } +} + +func (r *RPC) handlesMethod(methodName string) bool { + return reflect.ValueOf(r.serviceObject).MethodByName(methodName).IsValid() +} + +func (r *RPC) handlesPath(path string) bool { + return strings.HasPrefix(path, r.path) && r.handlesMethod(r.getMethodFromPath(path)) +} + +func (r *RPC) HandlesRequest(incomingRequest *http.Request) bool { + return incomingRequest.Method == "POST" && r.handlesPath(incomingRequest.URL.Path) +} + +func (r *RPC) callServiceObjectWithHTTPRequest(incomingRequest *http.Request) (reply *rpc.MethodReply) { + reply = &rpc.MethodReply{} + path := incomingRequest.URL.Path + argumentMap := extractPostData(incomingRequest) + methodName := r.getMethodFromPath(path) + arguments := r.extractArguments(path) + r.callServiceObject(methodName, arguments, argumentMap, reply) + return reply +} + +func (r *RPC) extractArguments(path string) (args []string) { + for _, value := range strings.Split(r.getApplicationPath(path), "/")[1:] { + unescapedArg, err := url.QueryUnescape(value) + if err != nil { + panic(err) + } + args = append(args, unescapedArg) + } + return args +} + +func (r *RPC) callServiceObject(methodName string, arguments []string, argumentMap map[string]interface{}, reply *rpc.MethodReply) { + reflectionArgs := []reflect.Value{} + reflectionArgs = append(reflectionArgs, reflect.ValueOf(arguments), reflect.ValueOf(argumentMap), reflect.ValueOf(reply)) + reflect.ValueOf(r.serviceObject).MethodByName(methodName).Call(reflectionArgs) +} + +func extractPostData(incomingRequest *http.Request) map[string]interface{} { + body, err := ioutil.ReadAll(incomingRequest.Body) + if err != nil { + panic(err) + } + return jsonDecode(body).(map[string]interface{}) +} + +func jsonDecode(jsonData []byte) (data interface{}) { + err := json.Unmarshal(jsonData, &data) + if err != nil { + panic(err) + } else { + return data + } +} + +func jsonEncode(data interface{}) []byte { + b, err := json.Marshal(data) + if err != nil { + panic(err) + } else { + return b + } +} + +func (r *RPC) ServeHTTP(w http.ResponseWriter, incomingRequest *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write(jsonEncode(r.callServiceObjectWithHTTPRequest(incomingRequest))) +} diff --git a/proxy/handler/rpc_test.go b/proxy/handler/rpc_test.go new file mode 100644 index 0000000..71ddbd9 --- /dev/null +++ b/proxy/handler/rpc_test.go @@ -0,0 +1,79 @@ +package handler + +import ( + "github.com/foomo/gofoomo/rpc" + "log" + "testing" +) + +type TestService struct { +} + +func NewTestService() *TestService { + t := new(TestService) + return t +} + +func getTestRPC() *RPC { + return NewRPC(NewTestService(), "/services/test.php") +} + +func (t *TestService) Test(arguments []string, argumentMap map[string]interface{}, reply *rpc.MethodReply) { + reply.Value = true +} + +func TestHandlesMethod(t *testing.T) { + r := getTestRPC() + if r.handlesMethod("Test") == false { + t.Fail() + } + if r.handlesMethod("testi") == true { + t.Fail() + } +} + +func TestGetApplicationPath(t *testing.T) { + p := getTestRPC().getApplicationPath("/services/test.php/Foomo.Services.RPC/serve/test") + if p != "test" { + t.Fatal("i do not like this path", p) + } +} + +func TestHandlesPath(t *testing.T) { + r := getTestRPC() + if r.handlesPath("/services/test.php/Foomo.Services.RPC/serve/test") == false { + t.Fatal("/services/test.php/Foomo.Services.RPC/serve/test") + } + if r.handlesPath("/services/test.php/Foomo.Services.RPC/serve/test/foo") == false { + t.Fatal("/services/test.php/Foomo.Services.RPC/serve/test/foo") + } + if r.handlesPath("/services/test.php/Foomo.Services.RPC/serve/testi/foo") == true { + t.Fatal("/services/test.php/Foomo.Services.RPC/serve/testi/foo") + } +} + +func TestExtractArguments(t *testing.T) { + r := getTestRPC() + args := r.extractArguments("/services/test.php/Foomo.Services.RPC/serve/test/%C3%BCb%C3%A4l/B%C3%A4r") + if len(args) != 2 { + t.Fatal("wrong args length", args) + } + if args[0] != "übäl" { + t.Fatal("no übäl") + } + if args[1] != "Bär" { + t.Fatal("where is the bear") + } +} + +func TestCallServiceObject(t *testing.T) { + r := getTestRPC() + var argumentMap map[string]interface{} + var arguments []string + reply := &rpc.MethodReply{} + r.callServiceObject("Test", arguments, argumentMap, reply) + if reply.Value != true { + log.Println(reply) + t.Fail() + } +} diff --git a/proxy/handler/staticfiles.go b/proxy/handler/staticfiles.go new file mode 100644 index 0000000..1bc6a0a --- /dev/null +++ b/proxy/handler/staticfiles.go @@ -0,0 +1,87 @@ +package handler + +import ( + "github.com/foomo/gofoomo/foomo" + "io" + "net/http" + "os" + "strings" +) + +// Handles serving static files from the local file system. It knows about +// foomos hierarchy and serves files from the htdocs directories of modules. +// Currently it will also serve files of disabled modules. + +type StaticFiles struct { + foomo *foomo.Foomo +} + +func NewStaticFiles(foomo *foomo.Foomo) *StaticFiles { + sf := new(StaticFiles) + sf.foomo = foomo + return sf +} + +func (files *StaticFiles) HandlesRequest(incomingRequest *http.Request) bool { + if strings.HasPrefix(incomingRequest.URL.Path, "/foomo/modules/") { + parts := strings.Split(incomingRequest.URL.Path, "/") + if len(parts) > 3 { + moduleNameParts := strings.Split(parts[3], "-") + return fileExists(files.foomo.GetModuleHtdocsDir(moduleNameParts[0]) + "/" + strings.Join(parts[4:], "/")) + } else { + return false + } + } else if strings.HasPrefix(incomingRequest.URL.Path, "/foomo/modulesVar/") { + return true + } else { + return false + } +} + +func fileExists(filename string) bool { + _, err := os.Stat(filename) + return err == nil +} + +func (files *StaticFiles) ServeHTTP(w http.ResponseWriter, incomingRequest *http.Request) { + parts := strings.Split(incomingRequest.URL.Path, "/") + path := strings.Join(parts[4:], "/") + moduleNameParts := strings.Split(parts[3], "-") + moduleName := moduleNameParts[0] + var moduleDir string + if strings.HasPrefix(incomingRequest.URL.Path, "/foomo/modules/") { + moduleDir = files.foomo.GetModuleHtdocsDir(moduleName) + } else { + moduleDir = files.foomo.GetModuleHtdocsVarDir(moduleName) + } + f, err := os.Open(moduleDir + "/" + path) + if err != nil { + panic(err) + } else { + defer f.Close() + w.Header().Set("Content-Type", getContentType(path)) + io.Copy(w, f) + } +} + +func getContentType(path string) string { + if strings.HasSuffix(path, ".png") { + return "image/png" + } else if strings.HasSuffix(path, ".jpg") { + return "image/jpeg" + } else if strings.HasSuffix(path, ".jpeg") { + return "image/jpeg" + } else if strings.HasSuffix(path, ".gif") { + return "image/gif" + } else if strings.HasSuffix(path, ".css") { + return "text/css" + } else if strings.HasSuffix(path, ".js") { + return "application/javascript" + } else if strings.HasSuffix(path, ".html") { + return "text/html" + } else if strings.HasSuffix(path, ".") { + return "" + } else { + return "octet/stream" + } +} diff --git a/proxy/proxy.go b/proxy/proxy.go new file mode 100644 index 0000000..2ec6c86 --- /dev/null +++ b/proxy/proxy.go @@ -0,0 +1,41 @@ +package proxy + +import ( + "github.com/foomo/gofoomo/foomo" + "net/http" + "net/http/httputil" +) + +type Handler interface { + HandlesRequest(incomingRequest *http.Request) bool + ServeHTTP(w http.ResponseWriter, incomingRequest *http.Request) +} + +type Proxy struct { + foomo *foomo.Foomo + reverseProxy *httputil.ReverseProxy + handlers []Handler +} + +func NewProxy(f *foomo.Foomo) *Proxy { + proxy := new(Proxy) + proxy.foomo = f + proxy.reverseProxy = httputil.NewSingleHostReverseProxy(proxy.foomo.URL) + return proxy +} + +func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, incomingRequest *http.Request) { + for _, handler := range proxy.handlers { + if handler.HandlesRequest(incomingRequest) { + handler.ServeHTTP(w, incomingRequest) + return + } + } + incomingRequest.Host = proxy.foomo.URL.Host + incomingRequest.URL.Opaque = incomingRequest.RequestURI + proxy.reverseProxy.ServeHTTP(w, incomingRequest) +} + +func (proxy *Proxy) AddHandler(handler Handler) { + proxy.handlers = append(proxy.handlers, handler) +} diff --git a/rpc/rpc.go b/rpc/rpc.go new file mode 100644 index 0000000..9ab1e3e --- /dev/null +++ b/rpc/rpc.go @@ -0,0 +1 @@ +package rpc diff --git a/rpc/value_objects.go b/rpc/value_objects.go new file mode 100644 index 0000000..837dacc --- /dev/null +++ b/rpc/value_objects.go @@ -0,0 +1,30 @@ +package rpc + +// from php class Foomo\Services\RPC\Protocol\Call\MethodCall +// serializing a method call +type MethodCall struct { + // id of the method call + Id string `json:"id"` + // name of the method to be called + Method string `json:"method"` + // the method call arguments + Arguments []struct { + Name string `json:"name"` + Value interface{} `json:"value"` + } `json:"arguments"` +} + +// from php class Foomo\Services\RPC\Protocol\Reply\MethodReply +// reply to a method call +type MethodReply struct { + // id of the method call + Id string `json:"id"` + // return value + Value interface{} `json:"value"` + // server side exception + Exception interface{} `json:"exception"` + // messages from the server + // possibly many of them + // possibly many types + Messages interface{} `json:"messages"` +}