mirror of
https://github.com/foomo/gofoomo.git
synced 2025-10-16 12:25:44 +00:00
initial package layout, first working parts
This commit is contained in:
commit
7dbb05bd74
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.*
|
||||||
|
!.git*
|
||||||
|
|
||||||
35
README.md
Normal file
35
README.md
Normal file
@ -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.
|
||||||
41
foomo/core/client.go
Normal file
41
foomo/core/client.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
61
foomo/core/client_test.go
Normal file
61
foomo/core/client_test.go
Normal file
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
100
foomo/foomo.go
Normal file
100
foomo/foomo.go
Normal file
@ -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
|
||||||
|
}
|
||||||
47
foomo/foomo_test.go
Normal file
47
foomo/foomo_test.go
Normal file
@ -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"))
|
||||||
|
}
|
||||||
30
foomo/token.go
Normal file
30
foomo/token.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
gofoomo.go
Normal file
13
gofoomo.go
Normal file
@ -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
|
||||||
2
proxy/handler/handler.go
Normal file
2
proxy/handler/handler.go
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// Common handlers for the gofoomo.proxy.
|
||||||
|
package handler
|
||||||
116
proxy/handler/rpc.go
Normal file
116
proxy/handler/rpc.go
Normal file
@ -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)))
|
||||||
|
}
|
||||||
79
proxy/handler/rpc_test.go
Normal file
79
proxy/handler/rpc_test.go
Normal file
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
87
proxy/handler/staticfiles.go
Normal file
87
proxy/handler/staticfiles.go
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
41
proxy/proxy.go
Normal file
41
proxy/proxy.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
1
rpc/rpc.go
Normal file
1
rpc/rpc.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package rpc
|
||||||
30
rpc/value_objects.go
Normal file
30
rpc/value_objects.go
Normal file
@ -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"`
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user