housekeeping: code cleanup, tests and hello travis

This commit is contained in:
Jan Halfar 2016-02-17 13:30:07 +01:00
parent a7f428b26d
commit 9164c6064e
29 changed files with 426 additions and 475 deletions

View File

@ -1,3 +1,5 @@
[![Travis CI](https://travis-ci.org/foomo/gofoomo.svg?branch=master)](https://travis-ci.org/foomo/gofoomo)
# gofoomo
Gofoomo lets you use Go in your foomo project. It also lets you use php in your Go project.
@ -30,6 +32,31 @@ Gofoomo lets you intercept and implement calls to foomo json rpc services. In ad
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.
## foomo-bert
Is a command line utility, that helps you with the setup of foomo installations.
```bash
go install github.com/foomo/gofoomo/foomo-bert
foomo-bert -help
usage: foomo-bert <command>
foomo-bert prepare :
-dir string
path/to/your/foomo/root
-run-mode string
foomo run mode test | development | production
foomo-bert reset :
-addr string
address of the foomo server
-dir string
path/to/your/foomo/root
-main-module string
name of main module (default "Foomo")
-run-mode string
foomo run mode test | development | production
```
## 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.

View File

@ -13,7 +13,7 @@ var flagConfig = flag.String("config", "", "config file")
func main() {
fmt.Println("I am a little demo program")
flag.Parse()
proxyServer, err := proxy.NewProxyServerWithConfig(*flagConfig)
proxyServer, err := proxy.NewServerWithConfig(*flagConfig)
if err != nil {
log.Fatal(err.Error())
}

View File

@ -8,7 +8,6 @@ import (
"os"
"path"
"github.com/bgentry/speakeasy"
"github.com/foomo/gofoomo/foomo"
"github.com/foomo/gofoomo/foomo/bert"
"github.com/foomo/gofoomo/utils"
@ -144,14 +143,12 @@ func main() {
os.Exit(1)
}
if len(*flagsPrepare.admin) > 0 {
fmt.Println("adding admin user", *flagsPrepare.admin)
passwd, err := speakeasy.Ask("enter password for " + *flagsPrepare.admin)
err = b.PrepareAdmin(*flagsPrepare.admin)
if err != nil {
fmt.Println("could not read password, giving up")
fmt.Println("could not set password for " + *flagsPrepare.admin + " " + err.Error())
os.Exit(1)
}
fmt.Println("adding password", passwd, "for", *flagsPrepare.admin)
os.Exit(0)
}
case "reset":
fs, flagsReset := foomoFlagsetReset()

View File

@ -1,17 +0,0 @@
package bert
import (
"io/ioutil"
"gopkg.in/yaml.v2"
)
func readConfig(filename string) (config map[string][]string, err error) {
config = make(map[string][]string)
yamlBytes, err := ioutil.ReadFile(filename)
if err != nil {
return
}
err = yaml.Unmarshal(yamlBytes, config)
return
}

View File

@ -1,34 +0,0 @@
package bert
import (
"path"
"testing"
"github.com/foomo/gofoomo/utils"
)
func TestReadConfig(t *testing.T) {
config, err := readConfig(path.Join(utils.GetCurrentDir(), "basicauth_test", "test.yml"))
utils.PanicOnErr(err)
expectedAuthDomains := map[string][]string{
"default": []string{
"secret/foo",
"secret/bar",
},
"sepp": []string{
"secret/sepp/test",
},
}
for expectedAuthDomain, secrets := range expectedAuthDomains {
configSecrets, ok := config[expectedAuthDomain]
if !ok {
t.Fatal("missing auth domain", expectedAuthDomain)
}
for index, secret := range secrets {
configSecret := configSecrets[index]
if configSecret != secret {
t.Fatal("unexpected secret", secret, "should be", configSecret)
}
}
}
}

View File

@ -1,3 +0,0 @@
jan:$2y$05$xHK1qpXKtLof8.5WYEQGVuR0GFuTdzcbjNHfUMhK6PjoU4TtD6DmW
test:$2y$05$aUToamwisvuLuRtFcpEvY./yT83dGwlMMa8bKD5IduagQwYIv4gym
foo:$2y$05$25AK9DVp0P4hpqRq5Zu4HeA0iWoaR/exloH07mKCrdZ4pMD0q8H6.

View File

@ -1,6 +0,0 @@
---
default:
- secret/foo
- secret/bar
sepp:
- secret/sepp/test

View File

@ -1,20 +1,19 @@
package bert
import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"path"
"github.com/foomo/gofoomo/foomo"
)
// Bert is a foomo helper
type Bert struct {
foomo *foomo.Foomo
}
// NewBert constructor
func NewBert(f *foomo.Foomo) *Bert {
return &Bert{
foomo: f,
@ -27,29 +26,7 @@ type moduleList struct {
const line = "-------------------------------------------------------------------------------"
func (b *Bert) Reset(mainModuleName string) error {
err := b.get("resetting everything", "/foomo/hiccup.php?class=hiccup&action=resetEverything")
if err != nil {
return errors.New("failed to reset everything: " + err.Error())
}
err = b.get("enabling main module "+mainModuleName, "/foomo/core.php/enableModule/"+mainModuleName)
if err != nil {
return errors.New("enabling main module failed" + err.Error())
}
err = b.get("trying to create missing module resources", "/foomo/core.php/tryCreateModuleResources")
if err != nil {
return errors.New("failed to create module resources" + err.Error())
}
err = b.get("running make clean all", "/foomo/core.php/make/clean,all")
if err != nil {
return errors.New("make clean, all failed: " + err.Error())
}
return nil
}
// plain http get
func (b *Bert) get(explanation string, path string) error {
fmt.Println(explanation)
fmt.Println(line)
@ -73,31 +50,3 @@ func (b *Bert) get(explanation string, path string) error {
fmt.Println(line)
return nil
}
func (b *Bert) Prepare() error {
b.foomo.GetVarDir()
dirs := []string{
"composer",
"docker",
path.Join("config", b.foomo.RunMode, "foomo"),
path.Join("var", b.foomo.RunMode),
path.Join("var", b.foomo.RunMode, "logs"),
path.Join("var", b.foomo.RunMode, "basicAuth"),
path.Join("var", b.foomo.RunMode, "sessions"),
path.Join("var", b.foomo.RunMode, "tmp"),
path.Join("var", b.foomo.RunMode, "logs"),
path.Join("var", b.foomo.RunMode, "cache"),
path.Join("var", b.foomo.RunMode, "modules"),
path.Join("var", b.foomo.RunMode, "htdocs", "modules"),
path.Join("var", b.foomo.RunMode, "htdocs", "modulesVar"),
}
for _, dir := range dirs {
dirToMake := path.Join(b.foomo.Root, dir)
mkdirErr := os.MkdirAll(dirToMake, 0744)
if mkdirErr != nil {
return mkdirErr
}
fmt.Println("created path", dirToMake)
}
return nil
}

63
foomo/bert/prepare.go Normal file
View File

@ -0,0 +1,63 @@
package bert
import (
"errors"
"fmt"
"os"
"path"
"github.com/bgentry/speakeasy"
"github.com/foomo/gofoomo/foomo"
"github.com/foomo/htpasswd"
)
// Prepare an installation (very basic skeleton)
func (b *Bert) Prepare() error {
b.foomo.GetVarDir()
dirs := []string{
"composer",
"docker",
path.Join("config", b.foomo.RunMode, "foomo"),
path.Join("var", b.foomo.RunMode),
path.Join("var", b.foomo.RunMode, "logs"),
path.Join("var", b.foomo.RunMode, "basicAuth"),
path.Join("var", b.foomo.RunMode, "sessions"),
path.Join("var", b.foomo.RunMode, "tmp"),
path.Join("var", b.foomo.RunMode, "logs"),
path.Join("var", b.foomo.RunMode, "cache"),
path.Join("var", b.foomo.RunMode, "modules"),
path.Join("var", b.foomo.RunMode, "htdocs", "modules"),
path.Join("var", b.foomo.RunMode, "htdocs", "modulesVar"),
}
for _, dir := range dirs {
dirToMake := path.Join(b.foomo.Root, dir)
mkdirErr := os.MkdirAll(dirToMake, 0744)
if mkdirErr != nil {
return mkdirErr
}
fmt.Println("created path", dirToMake)
}
return nil
}
// PrepareAdmin asks and adds basic auth credentials for foomo default auth\
// domain
func (b *Bert) PrepareAdmin(admin string) error {
fmt.Println("adding admin user", admin)
passwd, err := speakeasy.Ask("enter password for " + admin + " 🔑 :\n")
if err != nil {
return errors.New("could not read password, giving up")
}
passwordFile := b.foomo.GetBasicAuthFilename(foomo.DefaultBasicAuthDomainName)
fmt.Println("adding password for admin in:", passwordFile)
err = htpasswd.SetPassword(passwordFile, admin, passwd, htpasswd.HashBCrypt)
if err != nil {
return errors.New("could not write default basic auth password for admin: " + err.Error())
}
fmt.Println("added password")
fmt.Println("DONE")
return nil
}

27
foomo/bert/reset.go Normal file
View File

@ -0,0 +1,27 @@
package bert
import "errors"
// Reset reset a bert installation
func (b *Bert) Reset(mainModuleName string) error {
err := b.get("resetting everything", "/foomo/hiccup.php?class=hiccup&action=resetEverything")
if err != nil {
return errors.New("failed to reset everything: " + err.Error())
}
err = b.get("enabling main module "+mainModuleName, "/foomo/core.php/enableModule/"+mainModuleName)
if err != nil {
return errors.New("enabling main module failed" + err.Error())
}
err = b.get("trying to create missing module resources", "/foomo/core.php/tryCreateModuleResources")
if err != nil {
return errors.New("failed to create module resources" + err.Error())
}
err = b.get("running make clean all", "/foomo/core.php/make/clean,all")
if err != nil {
return errors.New("make clean, all failed: " + err.Error())
}
return nil
}

View File

@ -11,12 +11,12 @@ import (
)
func get(foomo *foomo.Foomo, path ...string) (data []byte, err error) {
callUrl := foomo.GetURLWithCredentialsForDefaultBasicAuthDomain()
callURL := foomo.GetURLWithCredentialsForDefaultBasicAuthDomain()
encodedPath := ""
for _, pathEntry := range path {
encodedPath += "/" + url.QueryEscape(pathEntry)
}
req, err := http.NewRequest("GET", callUrl+"/foomo/core.php"+encodedPath, nil)
req, err := http.NewRequest("GET", callURL+"/foomo/core.php"+encodedPath, nil)
if err != nil {
return
}
@ -36,19 +36,19 @@ func get(foomo *foomo.Foomo, path ...string) (data []byte, err error) {
return data, err
}
// GetJSON call into foomo and unmarshal response using encoding/json
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
}
return err
}
// GetConfig retrieve a domain config
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)
}
return GetJSON(foomo, target, "config", moduleName, configName, domain)
}

View File

@ -2,8 +2,15 @@ package core
import (
"encoding/json"
"github.com/foomo/gofoomo/foomo"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/foomo/gofoomo/foomo"
"github.com/foomo/gofoomo/foomo/bert"
)
type CoreConfig struct {
@ -15,9 +22,41 @@ type CoreConfig struct {
var testFoomo *foomo.Foomo
func poe(err error, msg string) {
if err != nil {
panic(msg + " : " + err.Error())
}
}
func getTestServer() *httptest.Server {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{
"enabledModules": [
"Foomo"
],
"availableModules": [
"Foomo"
]
}`))
}))
return ts
}
func getTestFoomo() *foomo.Foomo {
if testFoomo == nil {
f, _ := foomo.NewFoomo("/Users/jan/vagrant/schild/www/schild", "test", "http://schild-local-test.bestbytes.net")
ts := getTestServer()
tmp := os.TempDir()
dir, err := ioutil.TempDir(tmp, "dummy-foomo")
poe(err, "failed to get temp dir")
bareFoomo, err := foomo.BareFoomo(dir, "test")
poe(err, "failed to get bare foomo")
b := bert.NewBert(bareFoomo)
b.Prepare()
f, err := foomo.NewFoomo(dir, "test", fmt.Sprint(ts.URL))
if err != nil {
panic("invalid test foomo " + err.Error())
}
testFoomo = f
}
return testFoomo

View File

@ -1,25 +1,25 @@
package foomo
import (
"crypto/sha1"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"net/http"
u "net/url"
"os"
"strings"
"github.com/foomo/htpasswd"
)
const (
// RunModeTest run mode test
RunModeTest = "test"
// RunModeDevelopment run mode development
RunModeDevelopment = "development"
// RunModeProduction run mode production
RunModeProduction = "production"
// DefaultBasicAuthDomainName name of default basic auth domain
DefaultBasicAuthDomainName = "default"
)
const shaPrefix = "{SHA}"
// Foomo foomo go wrapper
type Foomo struct {
Root string
@ -72,118 +72,56 @@ func makeFoomo(foomoDir string, runMode string, address string, init bool) (f *F
if init {
authErr := f.setupBasicAuthCredentials()
if authErr != nil {
return nil, authErr
return nil, errors.New("can not set up auth: " + authErr.Error())
}
}
return f, err
}
func (f *Foomo) BasicAuth(domain string, user string, password string) bool {
for _, line := range strings.Split(f.getBasicAuthFileContentsForDomain(domain), "\n") {
lineParts := strings.Split(line, ":")
if len(lineParts) == 2 && lineParts[0] == user {
hash := getBasicAuthHash(password)
return hash == lineParts[1]
}
}
return false
}
func (f *Foomo) BasicAuthForRequest(w http.ResponseWriter, incomingRequest *http.Request, domain string, realm string, denialHTML string) bool {
forbidden := func() bool {
realm := strings.Replace(realm, "\"", "'", -1)
w.Header().Set("Www-Authenticate", "Basic realm=\""+realm+"\"")
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(denialHTML))
return false
}
authHeader := incomingRequest.Header.Get("Authorization")
if len(authHeader) == 0 {
return forbidden()
}
auth, base64DecodingErr := base64.StdEncoding.DecodeString(strings.TrimPrefix(authHeader, "Basic "))
if base64DecodingErr != nil {
return forbidden()
}
authParts := strings.Split(string(auth), ":")
if len(authParts) != 2 {
return forbidden()
}
if f.BasicAuth(domain, authParts[0], authParts[1]) {
return true
} else {
return forbidden()
}
}
func (f *Foomo) getBasicAuthFileContentsForDomain(domain string) string {
basicAuthFilename := f.GetBasicAuthFilename(domain)
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 if len(line) > 0 {
newLines = append(newLines, line)
}
}
newLines = append(newLines, user+":"+getBasicAuthHash(password))
return strings.Join(newLines, "\n") + "\n"
}
func getBasicAuthHash(password string) string {
s := sha1.New()
s.Write([]byte(password))
passwordSum := []byte(s.Sum(nil))
return shaPrefix + base64.StdEncoding.EncodeToString(passwordSum)
return htpasswd.SetPassword(f.GetBasicAuthFilename("default"), f.basicAuthCredentials.user, f.basicAuthCredentials.password, htpasswd.HashBCrypt)
}
// GetURLWithCredentialsForDefaultBasicAuthDomain i.e. sth. like http(s)://user:password@foomo-server.org(:8080)
func (f *Foomo) GetURLWithCredentialsForDefaultBasicAuthDomain() string {
url, _ := u.Parse(f.URL.String())
url.User = u.UserPassword(f.basicAuthCredentials.user, f.basicAuthCredentials.password)
return url.String()
}
// GetBasicAuthCredentialsForDefaultBasicAuthDomain user, password generated for the local foomo instance
func (f *Foomo) GetBasicAuthCredentialsForDefaultBasicAuthDomain() (user string, password string) {
return f.basicAuthCredentials.user, f.basicAuthCredentials.password
}
// GetModuleDir root dir of a module
func (f *Foomo) GetModuleDir(moduleName string, dir string) string {
return f.Root + "/modules/" + moduleName + "/" + dir
}
// GetVarDir root var dir for the current run mode
func (f *Foomo) GetVarDir() string {
return f.Root + "/var/" + f.RunMode
}
// GetModuleHtdocsDir htdocs dir in a module
func (f *Foomo) GetModuleHtdocsDir(moduleName string) string {
return f.GetModuleDir(moduleName, "htdocs")
}
// GetModuleCacheDir cache dir for a module
func (f *Foomo) GetModuleCacheDir(moduleName string) string {
return f.GetVarDir() + "/cache/" + moduleName
}
// GetModuleHtdocsVarDir module htdocs var dir
func (f *Foomo) GetModuleHtdocsVarDir(moduleName string) string {
return f.GetVarDir() + "/htdocs/modulesVar/" + moduleName
}
// GetBasicAuthFilename basic auth file name for a domain
func (f *Foomo) GetBasicAuthFilename(domain string) string {
return f.GetVarDir() + "/basicAuth/" + domain
}

View File

@ -1,23 +1,22 @@
package foomo
import (
"strings"
"os"
"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)
tempDir := os.TempDir()
f, err := BareFoomo(tempDir[0:len(tempDir)-1], "test")
if err != nil {
panic(err)
}
return f
}
func assertTempPath(t *testing.T, topic string, expected string, actual string) {
assertStringsEqual(t, topic, os.TempDir()+expected, actual)
}
func assertStringsEqual(t *testing.T, topic string, expected string, actual string) {
if actual != expected {
t.Fatal(topic, "actual: ", actual, " != expected: ", expected)
@ -26,22 +25,42 @@ func assertStringsEqual(t *testing.T, topic string, expected string, actual stri
func TestGetVarDir(t *testing.T) {
actual := getTestFoomoForFSStuff().GetVarDir()
expected := "/var/www/foomo/var/test"
assertStringsEqual(t, "var dir", expected, actual)
expected := "var/test"
assertTempPath(t, "var dir", expected, actual)
}
func TestGetModuleDir(t *testing.T) {
assertStringsEqual(t, "module dir", "/var/www/foomo/modules/Foomo/htdocs", getTestFoomoForFSStuff().GetModuleDir("Foomo", "htdocs"))
assertTempPath(
t,
"module dir",
"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"))
assertTempPath(
t,
"module htdocs dir",
"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"))
assertTempPath(
t,
"module htdocs var dir",
"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"))
assertTempPath(
t,
"basic auth file",
"var/test/basicAuth/sepp",
getTestFoomoForFSStuff().GetBasicAuthFilename("sepp"),
)
}

View File

@ -5,32 +5,26 @@ import (
"fmt"
"io/ioutil"
"github.com/foomo/tlsconfig"
"gopkg.in/yaml.v2"
)
// Auth basic auth
type Auth struct {
Domain string
Realm string
}
// TLS proxy tls config vo
type TLS struct {
Mode string
Mode tlsconfig.TLSModeServer
Address string
CertFile string
KeyFile string
}
const (
// this is serious and we do not mind loosing clients (= Mozilla "modern" compatibility)
// Compatible clients have versions equal or greater than Firefox 27, Chrome 22, IE 11, Opera 14, Safari 7, Android 4.4, Java 8
TLSModeStrict = "strict"
// ecommerce compromise
// Compatible clients (>=): Firefox 1, Chrome 1, IE 7, Opera 5, Safari 1, Windows XP IE8, Android 2.3, Java 7
TLSModeLoose = "loose"
// standard crypto/tls.Config un touched - highly compatible, but possibly insecure
TLSModeDefault = "default"
)
// Config proxy configuration
type Config struct {
// how should the proxy server run
Server struct {
@ -51,6 +45,7 @@ type Config struct {
AppOptions map[string]string
}
// ReadConfig from a file
func ReadConfig(filename string) (config *Config, err error) {
config = &Config{}
yamlBytes, err := ioutil.ReadFile(filename)
@ -62,12 +57,12 @@ func ReadConfig(filename string) (config *Config, err error) {
return nil, err
}
if config.Server.TLS.Mode == "" {
config.Server.TLS.Mode = TLSModeDefault
config.Server.TLS.Mode = tlsconfig.TLSModeServerDefault
}
switch config.Server.TLS.Mode {
case TLSModeDefault, TLSModeLoose, TLSModeStrict:
case tlsconfig.TLSModeServerDefault, tlsconfig.TLSModeServerLoose, tlsconfig.TLSModeServerStrict:
default:
return nil, errors.New("unknown server.tls.mode: " + config.Server.TLS.Mode + " - must be one of: " + fmt.Sprintln([]string{TLSModeDefault, TLSModeLoose, TLSModeStrict}))
return nil, errors.New("unknown server.tls.mode: " + string(config.Server.TLS.Mode) + " - must be one of: " + fmt.Sprintln([]tlsconfig.TLSModeServer{tlsconfig.TLSModeServerDefault, tlsconfig.TLSModeServerLoose, tlsconfig.TLSModeServerStrict}))
}
return config, nil
}

View File

@ -1,38 +1,53 @@
package handler
import (
"net/http"
"github.com/abbot/go-http-auth"
"github.com/foomo/gofoomo/foomo"
"github.com/foomo/gofoomo/proxy"
"log"
"net/http"
)
// AuthWrapper wraps proxy handlers with a basic authentication
type AuthWrapper struct {
handler proxy.Handler
foomo *foomo.Foomo
authDomain string
// authDomain string
denialHTML string
realm string
//realm string
authenticator *auth.BasicAuth
}
// NewAuthWrapper wrap a proxy handler with basic auth
func NewAuthWrapper(f *foomo.Foomo, h proxy.Handler, authDomain string, realm string, denialHTML string) *AuthWrapper {
secretProvider := auth.HtpasswdFileProvider(f.GetBasicAuthFilename(authDomain))
return &AuthWrapper{
handler: h,
foomo: f,
realm: realm,
authDomain: authDomain,
// realm: realm,
// authDomain: authDomain,
denialHTML: denialHTML,
authenticator: auth.NewBasicAuthenticator(realm, secretProvider),
}
}
// HandlesRequest am I responsible to handle that request
func (a *AuthWrapper) HandlesRequest(incomingRequest *http.Request) bool {
// asking my underlying handler
return a.handler.HandlesRequest(incomingRequest)
}
func (a *AuthWrapper) ServeHTTP(w http.ResponseWriter, incomingRequest *http.Request) {
if !a.foomo.BasicAuthForRequest(w, incomingRequest, a.authDomain, "authenticate", "nope") {
log.Println("auth wrapper access denied for", incomingRequest.RequestURI)
func (a *AuthWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
user := a.authenticator.CheckAuth(r)
if len(user) == 0 {
a.authenticator.RequireAuth(w, r)
}
user = a.authenticator.CheckAuth(r)
if len(user) == 0 {
w.Write([]byte(a.denialHTML))
return
}
a.handler.ServeHTTP(w, incomingRequest)
a.handler.ServeHTTP(w, r)
}

View File

@ -2,14 +2,16 @@ package images
import (
"errors"
"github.com/foomo/gofoomo/foomo"
"log"
"net/http"
"os"
"strings"
"time"
"github.com/foomo/gofoomo/foomo"
)
// ImageInfo image cache meta data
type ImageInfo struct {
Filename string
Etag string
@ -18,6 +20,7 @@ type ImageInfo struct {
Expires int64
}
// NewImageInfo constructor
func NewImageInfo(response *http.Response) *ImageInfo {
i := new(ImageInfo)
if response != nil {
@ -35,11 +38,13 @@ func (i *ImageInfo) getHeader(name string) []string {
return h
}
// ImageRequestResult result vo
type ImageRequestResult struct {
Error error
Request *ImageRequest
}
// ImageRequest request vo
type ImageRequest struct {
Id string
IncomingRequest *http.Request
@ -48,6 +53,7 @@ type ImageRequest struct {
ImageInfo *ImageInfo
}
// NewImageRequest image request constructor
func NewImageRequest(id string, incomingRequest *http.Request, foomoMediaClientInfoCookie *http.Cookie) *ImageRequest {
r := new(ImageRequest)
r.Id = id
@ -64,6 +70,7 @@ func (i *ImageRequest) execute(cache *Cache) error {
return result.Error
}
// Cache image cache
type Cache struct {
Directory map[string]*ImageInfo
Foomo *foomo.Foomo
@ -75,6 +82,7 @@ type Cache struct {
//doneChannel chan *ImageRequest
}
// NewCache cache constructor
func NewCache(f *foomo.Foomo) *Cache {
c := new(Cache)
c.Foomo = f
@ -124,6 +132,7 @@ func (c *Cache) runLoop() {
}
// Get get image with the given breakpoints
func (c *Cache) Get(request *http.Request, breakPoints []int64) (info *ImageInfo, err error) {
cookie := getFoomoMediaClientInfoCookie(request.Cookies(), breakPoints)
@ -174,7 +183,7 @@ func (c *Cache) getImage(incomingRequest *http.Request, foomoMediaClientInfoCook
if err != nil {
return NewImageInfo(nil), err
} else {
}
request.AddCookie(foomoMediaClientInfoCookie)
if c.foomoSessionCookie != nil {
request.AddCookie(c.foomoSessionCookie)
@ -183,17 +192,16 @@ func (c *Cache) getImage(incomingRequest *http.Request, foomoMediaClientInfoCook
request.URL.Host = c.Foomo.URL.Host
request.URL.Scheme = c.Foomo.URL.Scheme
imageServerResponse, err := c.client.Do(request)
i := NewImageInfo(imageServerResponse)
i = NewImageInfo(imageServerResponse)
if imageServerResponse != nil {
defer imageServerResponse.Body.Close()
}
if err != nil {
if imageServerResponse != nil && imageServerResponse.StatusCode == http.StatusMovedPermanently {
return nil, errors.New("unexpected redirect")
} else {
}
return nil, errors.New("unexpected error " + err.Error())
}
} else {
i.StatusCode = imageServerResponse.StatusCode
c.checkFoomoSessionCookie(imageServerResponse)
@ -216,7 +224,6 @@ func (c *Cache) getImage(incomingRequest *http.Request, foomoMediaClientInfoCook
default:
return nil, errors.New("unexpected reply with status " + imageServerResponse.Status)
}
}
return i, nil
}
}

View File

@ -1,16 +1,19 @@
package images
import (
"github.com/foomo/gofoomo/foomo"
"github.com/foomo/gofoomo/foomo/core"
"sort"
"strconv"
"github.com/foomo/gofoomo/foomo"
"github.com/foomo/gofoomo/foomo/core"
)
// MediaServerConfig foomo config helper
type MediaServerConfig struct {
Grid map[string]map[string]int64 `json:"grid"`
}
// SessionConfig foomo config helper
type SessionConfig struct {
Name string `json:"name"`
}
@ -24,7 +27,7 @@ func getBreakPoints(f *foomo.Foomo) []int64 {
}
breakPointsInt := []int{}
for breakPointString, _ := range c.Grid {
for breakPointString := range c.Grid {
breakPointInt, _ := strconv.Atoi(breakPointString)
if breakPointInt > 0 {
breakPointsInt = append(breakPointsInt, breakPointInt)

View File

@ -10,18 +10,24 @@ import (
)
const (
// DefaultScreenWidth still 1024
DefaultScreenWidth = 1024
// DefaultScreenHeight like in the good old days
DefaultScreenHeight = 768
// DefaultPixelRatio in case of doubt lightweight
DefaultPixelRatio = 1.0
// FoomoMediaClientInfoCookieName that is the name of the cookie we inspect to extract the client informattions from
FoomoMediaClientInfoCookieName = "foomoMediaClientInfo"
)
// ClientInfo vo
type ClientInfo struct {
screenWidth int64
screenHeight int64
pixelRatio float64
}
// NewClientInfo constructor
func NewClientInfo() *ClientInfo {
info := new(ClientInfo)
info.screenWidth = DefaultScreenWidth
@ -36,7 +42,7 @@ func readFoomoMediaClientInfo(cookie string) (clientInfo *ClientInfo, err error)
if len(parts) != 2 {
err = errors.New(fmt.Sprint("could not separate screen size from pixel ratio", parts))
return clientInfo, err
} else {
}
screenSizeParts := strings.Split(parts[0], "x")
if len(screenSizeParts) != 2 {
err = errors.New(fmt.Sprint("could not find screen size components ", len(screenSizeParts), " in ", parts[0]))
@ -47,7 +53,6 @@ func readFoomoMediaClientInfo(cookie string) (clientInfo *ClientInfo, err error)
clientInfo.screenHeight, _ = strconv.ParseInt(screenSizeParts[1], 0, 32)
}
return clientInfo, err
}
}
func clampScreenWidthToGrid(screenWidth int64, breakPoints []int64) int64 {

View File

@ -2,21 +2,23 @@ package images
import (
"errors"
"github.com/foomo/gofoomo/foomo"
//"io"
//"log"
"net/http"
"os"
"strings"
"time"
"github.com/foomo/gofoomo/foomo"
)
// Adaptive adaptive images
type Adaptive struct {
Paths []string
BreakPoints []int64
Cache *Cache
}
// NewAdaptive constructor
func NewAdaptive(paths []string, f *foomo.Foomo) *Adaptive {
a := new(Adaptive)
a.Paths = paths
@ -25,6 +27,7 @@ func NewAdaptive(paths []string, f *foomo.Foomo) *Adaptive {
return a
}
// HandlesRequest request handler interface implementation
func (a *Adaptive) HandlesRequest(incomingRequest *http.Request) bool {
for _, p := range a.Paths {
if strings.HasPrefix(incomingRequest.URL.Path, p) {
@ -35,19 +38,13 @@ func (a *Adaptive) HandlesRequest(incomingRequest *http.Request) bool {
}
func (a *Adaptive) ServeHTTP(w http.ResponseWriter, incomingRequest *http.Request) {
info, err := a.Cache.Get(incomingRequest, a.BreakPoints)
//log.Println("info for request" + incomingRequest.RequestURI)
//log.Println(info)
//log.Println("------------------------------------")
if err != nil {
panic(err)
}
if info == nil {
panic(errors.New("could not get image"))
} else {
}
// 304 handling
file, err := os.Open(info.Filename)
if err != nil {
@ -60,32 +57,8 @@ func (a *Adaptive) ServeHTTP(w http.ResponseWriter, incomingRequest *http.Reques
panic(err)
}
w.Header().Set("Expires", time.Now().Add(time.Hour * 24 * 7).Format(http.TimeFormat))
//this part handles etags in parallel with expires
/* browserEtag := incomingRequest.Header.Get("If-None-Match")
if browserEtag == info.Etag {
w.WriteHeader(http.StatusNotModified)
writeHeaders(w, info)
} else {
writeHeaders(w, info)
file, err := os.Open(info.Filename)
if err != nil {
// dummy image ?!
panic(errors.New("could not open image file " + info.Filename + " " + err.Error()))
} else {
io.Copy(w, file)
defer file.Close()
}
}
//end of ETag handling
*/
w.Header().Set("Expires", time.Now().Add(time.Hour*24*7).Format(http.TimeFormat))
http.ServeContent(w, incomingRequest, file.Name(), fileInfo.ModTime(), file)
}
}
func writeHeaders(w http.ResponseWriter, info *ImageInfo) {
@ -93,15 +66,8 @@ func writeHeaders(w http.ResponseWriter, info *ImageInfo) {
if key == "Set-Cookie" {
continue
}
/*if key == "Expires" {
//force browser cache for 1 year
w.Header().Add(key, time.Now().Add(time.Hour*24*365).Format(http.TimeFormat))
}*/
for _, value := range values {
w.Header().Add(key, value)
}
}
}

View File

@ -6,7 +6,7 @@ import (
func TestReadFoomoMediaClientInfo(t *testing.T) {
// 2560x1440@0.8999999761581421
info, err := ReadFoomoMediaClientInfo("2560x1440@0.8999999761581421")
info, err := readFoomoMediaClientInfo("2560x1440@0.8999999761581421")
if err != nil {
t.Log("parse error", err)
t.Fail()
@ -28,11 +28,11 @@ func TestClampScreenWidthToGrid(t *testing.T) {
t.Error("expected", expected, "actual", actual)
}
}
assertSize(t, 320, ClampScreenWidthToGrid(298, breakPoints))
assertSize(t, 768, ClampScreenWidthToGrid(321, breakPoints))
assertSize(t, 768, ClampScreenWidthToGrid(766, breakPoints))
assertSize(t, 1024, ClampScreenWidthToGrid(1024, breakPoints))
assertSize(t, 1440, ClampScreenWidthToGrid(1440, breakPoints))
assertSize(t, 1440, ClampScreenWidthToGrid(1441, breakPoints))
assertSize(t, 1440, ClampScreenWidthToGrid(10000, breakPoints))
assertSize(t, 320, clampScreenWidthToGrid(298, breakPoints))
assertSize(t, 768, clampScreenWidthToGrid(321, breakPoints))
assertSize(t, 768, clampScreenWidthToGrid(766, breakPoints))
assertSize(t, 1024, clampScreenWidthToGrid(1024, breakPoints))
assertSize(t, 1440, clampScreenWidthToGrid(1440, breakPoints))
assertSize(t, 1440, clampScreenWidthToGrid(1441, breakPoints))
assertSize(t, 1440, clampScreenWidthToGrid(10000, breakPoints))
}

View File

@ -2,17 +2,18 @@ package handler
import (
"encoding/json"
"github.com/foomo/gofoomo/proxy/utils"
"github.com/foomo/gofoomo/rpc"
"io"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"strings"
"github.com/foomo/gofoomo/proxy/utils"
"github.com/foomo/gofoomo/rpc"
)
// This handler helps you to hijack foomo rpc services. Actually it is even
// RPC 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")
@ -27,6 +28,7 @@ type RPC struct {
serviceObject interface{}
}
// NewRPC rpc constructor, path is the path in the url, that you intend to hijack
func NewRPC(serviceObject interface{}, path string) *RPC {
rpc := new(RPC)
rpc.path = path
@ -42,9 +44,8 @@ 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 ""
}
return ""
}
func (r *RPC) handlesMethod(methodName string) bool {
@ -55,6 +56,7 @@ func (r *RPC) handlesPath(path string) bool {
return strings.HasPrefix(path, r.path) && r.handlesMethod(r.getMethodFromPath(path))
}
// HandlesRequest implementation of request handler interface
func (r *RPC) HandlesRequest(incomingRequest *http.Request) bool {
return incomingRequest.Method == "POST" && r.handlesPath(incomingRequest.URL.Path)
}
@ -93,9 +95,8 @@ func extractPostData(incomingRequest *http.Request) map[string]interface{} {
}
if len(body) > 0 {
return jsonDecode(body).(map[string]interface{})
} else {
return make(map[string]interface{})
}
return make(map[string]interface{})
}
func jsonDecode(jsonData []byte) (data interface{}) {

View File

@ -1,9 +1,10 @@
package handler
import (
"github.com/foomo/gofoomo/rpc"
"log"
"testing"
"github.com/foomo/gofoomo/rpc"
)
type TestService struct {

View File

@ -1,46 +1,46 @@
package handler
import (
"github.com/foomo/gofoomo/foomo"
"github.com/foomo/gofoomo/proxy/utils"
"net/http"
"os"
"strings"
"time"
"github.com/foomo/gofoomo/foomo"
"github.com/foomo/gofoomo/proxy/utils"
)
// Handles serving static files from the local file system. It knows about
// StaticFiles 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
}
// NewStaticFiles constructor
func NewStaticFiles(foomo *foomo.Foomo) *StaticFiles {
sf := new(StaticFiles)
sf.foomo = foomo
return sf
}
// HandlesRequest request handler implementation
func (files *StaticFiles) HandlesRequest(incomingRequest *http.Request) bool {
if strings.HasPrefix(incomingRequest.URL.Path, "/foomo/modulesVar/") {
return true
}
if strings.HasPrefix(incomingRequest.URL.Path, "/foomo/modules/") {
parts := strings.Split(incomingRequest.URL.Path, "/")
if len(parts) > 3 {
moduleNameParts := strings.Split(parts[3], "-")
if strings.HasSuffix(parts[len(parts)-1], ".php") {
return false
} else {
}
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 {
@ -76,26 +76,14 @@ func (files *StaticFiles) ServeHTTP(w http.ResponseWriter, incomingRequest *http
panicOnErr(err)
//const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
w.Header().Set("Expires", time.Now().Add(time.Hour*24*365).Format(http.TimeFormat))
// should we really compress all static file types ?!
if compress && strings.Contains(incomingRequest.Header.Get("Accept-Encoding"), "gzip") {
w.Header().Set("Content-Encoding", "gzip")
crw := utils.NewCompressedResponseWriter(w)
defer crw.Close()
w = crw
}
http.ServeContent(w, incomingRequest, f.Name(), fileInfo.ModTime(), f)
/* if compress {
err := utils.ServeCompressed(w, incomingRequest, func(writer io.Writer) error {
_, err := io.Copy(writer, f)
return err
})
panicOnErr(err)
} else {
_, err := io.Copy(w, f)
panicOnErr(err)
}
*/
}
func getContentType(path string) (string, bool) {

View File

@ -8,18 +8,22 @@ import (
"net/http/httputil"
"github.com/foomo/gofoomo/foomo"
"github.com/foomo/tlsconfig"
)
// Handler proxy handler
type Handler interface {
HandlesRequest(incomingRequest *http.Request) bool
ServeHTTP(w http.ResponseWriter, incomingRequest *http.Request)
}
// Listener deprecated
type Listener interface {
ListenServeHTTPStart(w http.ResponseWriter, incomingRequest *http.Request) http.ResponseWriter
ListenServeHTTPDone(w http.ResponseWriter, incomingRequest *http.Request)
}
// Proxy foomo proxy
type Proxy struct {
foomo *foomo.Foomo
ReverseProxy *httputil.ReverseProxy
@ -29,13 +33,15 @@ type Proxy struct {
ServeHTTPFunc func(http.ResponseWriter, *http.Request)
}
type ProxyServer struct {
// Server server for Proxy
type Server struct {
Proxy *Proxy
Config *Config
TLSConfig *tls.Config
Foomo *foomo.Foomo
}
// NewProxy constructor
func NewProxy(f *foomo.Foomo) *Proxy {
proxy := &Proxy{
foomo: f,
@ -50,8 +56,9 @@ func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, incomingRequest *http.Reque
}
func (proxy *Proxy) serveHTTP(w http.ResponseWriter, incomingRequest *http.Request) {
if proxy.auth != nil && len(proxy.auth.Domain) > 0 && !proxy.foomo.BasicAuthForRequest(w, incomingRequest, proxy.auth.Domain, proxy.auth.Realm, "access denied") {
return
if proxy.auth != nil && len(proxy.auth.Domain) > 0 {
//&& !proxy.foomo.BasicAuthForRequest(w, incomingRequest, proxy.auth.Domain, proxy.auth.Realm, "access denied") {
}
for _, listener := range proxy.listeners {
w = listener.ListenServeHTTPStart(w, incomingRequest)
@ -72,82 +79,44 @@ func (proxy *Proxy) serveHTTP(w http.ResponseWriter, incomingRequest *http.Reque
}
// AddHandler add a handler
func (proxy *Proxy) AddHandler(handler Handler) {
proxy.handlers = append(proxy.handlers, handler)
}
// AddListener deprecated
func (proxy *Proxy) AddListener(listener Listener) {
proxy.listeners = append(proxy.listeners, listener)
}
func NewProxyServerWithConfig(filename string) (p *ProxyServer, err error) {
// NewServerWithConfig constructor with a config file
func NewServerWithConfig(filename string) (p *Server, err error) {
config, err := ReadConfig(filename)
if err != nil {
return nil, err
}
return NewProxyServer(config)
return NewServer(config)
}
func NewProxyServer(config *Config) (p *ProxyServer, err error) {
proxyServer := new(ProxyServer)
proxyServer.Config = config
// NewServer constructor with config struct
func NewServer(config *Config) (p *Server, err error) {
p = &Server{
Config: config,
}
f, err := foomo.NewFoomo(config.Foomo.Dir, config.Foomo.RunMode, config.Foomo.Address)
if err != nil {
return nil, err
}
proxyServer.Foomo = f
proxyServer.Proxy = NewProxy(proxyServer.Foomo)
proxyServer.Proxy.auth = config.Server.Auth
proxyServer.TLSConfig = setupTLSConfig(proxyServer.Config.Server.TLS)
return proxyServer, nil
p.Foomo = f
p.Proxy = NewProxy(p.Foomo)
p.Proxy.auth = config.Server.Auth
p.TLSConfig = tlsconfig.NewServerTLSConfig(p.Config.Server.TLS.Mode)
return p, nil
}
func setupTLSConfig(tlsConfig TLS) *tls.Config {
c := &tls.Config{}
switch tlsConfig.Mode {
case TLSModeDefault:
// will not touch this one, but trust the golang team
case TLSModeLoose:
c.MinVersion = tls.VersionTLS10
c.CipherSuites = []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
}
c.CurvePreferences = []tls.CurveID{
tls.CurveP256,
tls.CurveP384,
tls.CurveP521,
}
case TLSModeStrict:
c.MinVersion = tls.VersionTLS12
c.PreferServerCipherSuites = true
c.CipherSuites = []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
}
c.CurvePreferences = []tls.CurveID{
tls.CurveP256,
tls.CurveP384,
tls.CurveP521,
}
}
return c
}
func (p *ProxyServer) ListenAndServe() error {
// ListenAndServe until things go bad, depending upon configuration this will\
// listen to http and https requests
func (p *Server) ListenAndServe() error {
c := p.Config.Server
errorChan := make(chan error)
startedHTTPS := false
@ -161,7 +130,6 @@ func (p *ProxyServer) ListenAndServe() error {
TLSConfig: p.TLSConfig,
}
errorChan <- tlsServer.ListenAndServeTLS(c.TLS.CertFile, c.TLS.KeyFile)
// errorChan <- http.ListenAndServeTLS(c.TLS.Address, c.TLS.CertFile, c.TLS.KeyFile, p.Proxy)
}()
startedHTTPS = true
}

View File

@ -2,16 +2,16 @@ package utils
import (
"compress/gzip"
//"io"
"net/http"
//"strings"
)
// CompressedResponseWriter helper to write compressed responses
type CompressedResponseWriter struct {
responseWriter http.ResponseWriter
gz *gzip.Writer
}
// NewCompressedResponseWriter constructor
func NewCompressedResponseWriter(responseWriter http.ResponseWriter) *CompressedResponseWriter {
crw := &CompressedResponseWriter{
responseWriter: responseWriter,
@ -45,6 +45,7 @@ func (crw *CompressedResponseWriter) WriteHeader(code int) {
crw.responseWriter.WriteHeader(code)
}
// Close me please
func (crw *CompressedResponseWriter) Close() {
crw.gz.Close()
}

View File

@ -7,6 +7,7 @@ import (
"strings"
)
// ServeCompressed serve a request with a compressed response, if the client accepts it
func ServeCompressed(w http.ResponseWriter, incomingRequest *http.Request, writeCallback func(writer io.Writer) error) error {
var writer io.Writer
writer = w
@ -20,8 +21,10 @@ func ServeCompressed(w http.ResponseWriter, incomingRequest *http.Request, write
}
// Resource304 vo
type Resource304 struct {
Etag string
}
// Handle304 304 handler
func Handle304(r *http.Request) {}

View File

@ -1,10 +1,10 @@
package rpc
// from php class Foomo\Services\RPC\Protocol\Call\MethodCall
// MethodCall 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"`
ID string `json:"id"`
// name of the method to be called
Method string `json:"method"`
// the method call arguments
@ -14,11 +14,11 @@ type MethodCall struct {
} `json:"arguments"`
}
// from php class Foomo\Services\RPC\Protocol\Reply\MethodReply
// MethodReply 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"`
ID string `json:"id"`
// return value
Value interface{} `json:"value"`
// server side exception

View File

@ -1 +0,0 @@
package utils