commit ec72b5b91a40bb7494687a1ce80ffc50f3714e65 Author: didier.amyot Date: Fri Sep 19 20:02:10 2014 -0700 copied from a project that was version on mercurial diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..9a30aeb --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,5 @@ +js/ +bower_components/ +node_modules/ +lib/ + diff --git a/client/Gruntfile.coffee b/client/Gruntfile.coffee new file mode 100644 index 0000000..478a3a2 --- /dev/null +++ b/client/Gruntfile.coffee @@ -0,0 +1,48 @@ +module.exports = (grunt) -> + + # Project configuration. + grunt.initConfig( + packagekg: grunt.file.readJSON('package.json') + bower: + install: {} + + connect: + server: + options: + port: 4506 + base: '.' + keepalive: true + + clean: ["js"] + + coffee: + compile: + expand: true + cwd: 'coffee' + src: ['**/*.coffee'] + dest: 'js' + ext: '.js' + + watch: + coffee: + files: ['**/*.coffee'] + tasks: ['coffee'] + options: + spawn: false + + concurrent: + target: ['watch', 'connect'], + + ) + + # Plugins + grunt.loadNpmTasks('grunt-bower-task') + grunt.loadNpmTasks('grunt-contrib-connect') + grunt.loadNpmTasks('grunt-contrib-coffee') + grunt.loadNpmTasks('grunt-contrib-watch') + grunt.loadNpmTasks('grunt-concurrent') + grunt.loadNpmTasks('grunt-contrib-clean') + + # tasks + grunt.registerTask('default', ['bower:install', 'clean', 'coffee:compile']) + grunt.registerTask('dev', ['default', 'concurrent:target']) diff --git a/client/bower.json b/client/bower.json new file mode 100644 index 0000000..e56b1ee --- /dev/null +++ b/client/bower.json @@ -0,0 +1,22 @@ +{ + "name": "bab", + "version": "0.1", + "authors": [ + "didier.amyot " + ], + "description": "Easy home to all your web application.", + "main": "index.html", + "license": "MIT", + "private": true, + "dependencies": { + "underscore": "~1.6.0", + "foundation": "~5.2.1", + "angular": "~1.2.14", + "angular-route": "~1.2.14", + "angular-local-storage": "~0.0.1", + "qunit": "~1.14.0", + "zepto": "~1.1.3", + "angular-sanitize": "~1.2.16", + "fontawesome": "~4.0.3" + } +} diff --git a/client/coffee/babApp.coffee b/client/coffee/babApp.coffee new file mode 100644 index 0000000..c4ed33f --- /dev/null +++ b/client/coffee/babApp.coffee @@ -0,0 +1,37 @@ + +root = this; + +angular.module('bab', [ + 'ngRoute' + 'bab.filters' + 'bab.services' + 'bab.directives' + 'bab.controllers' +]). +config(['$routeProvider', ($routeProvider) -> + $routeProvider.when('/', {templateUrl: 'partials/home.html', controller: 'homeCtrl'}) + $routeProvider.when('/login', {templateUrl: 'partials/login.html', controller: 'loginCtrl'}) + $routeProvider.when('/about', {templateUrl: 'partials/about.html', controller: 'aboutCtrl'}) + $routeProvider.otherwise({redirectTo: '/'}) +]) + +root.filters = angular.module('bab.filters', []) +root.services = angular.module('bab.services', []) +root.directives = angular.module('bab.directives', []) +root.controllers = angular.module('bab.controllers', []) + + +root.filters.filter 'month', () -> + (text) -> + return ['janvier', + 'février', + 'mars', + 'avril', + 'may', + 'juin', + 'juillet', + 'aout', + 'september', + 'octobre', + 'novembre', + 'decembre'][Number(text) - 1] \ No newline at end of file diff --git a/client/coffee/controllers/aboutCtrl.coffee b/client/coffee/controllers/aboutCtrl.coffee new file mode 100644 index 0000000..0dd93f1 --- /dev/null +++ b/client/coffee/controllers/aboutCtrl.coffee @@ -0,0 +1,4 @@ +root = this; + +root.controllers.controller('aboutCtrl', ['$scope', ($scope) -> +]) \ No newline at end of file diff --git a/client/coffee/controllers/homeCtrl.coffee b/client/coffee/controllers/homeCtrl.coffee new file mode 100644 index 0000000..8129786 --- /dev/null +++ b/client/coffee/controllers/homeCtrl.coffee @@ -0,0 +1,8 @@ +root = this; + + +root.controllers.controller('homeCtrl', ['$scope', '$http', ($scope, $http) -> + $scope.services = [] + $http.get('/webapps').then (resp) -> + $scope.services = resp.data +]) \ No newline at end of file diff --git a/client/coffee/controllers/loginCtrl.coffee b/client/coffee/controllers/loginCtrl.coffee new file mode 100644 index 0000000..b0b2323 --- /dev/null +++ b/client/coffee/controllers/loginCtrl.coffee @@ -0,0 +1,4 @@ +root = this; + +root.controllers.controller('loginCtrl', ['$scope', ($scope) -> +]) \ No newline at end of file diff --git a/client/coffee/controllers/topBarCtrl.coffee b/client/coffee/controllers/topBarCtrl.coffee new file mode 100644 index 0000000..141b524 --- /dev/null +++ b/client/coffee/controllers/topBarCtrl.coffee @@ -0,0 +1,9 @@ +root = this; + +root.controllers.controller('topBarCtrl', ['$scope', ($scope) -> + $scope.$on('$routeChangeSuccess', (next, current) -> + $scope.path = current.originalPath + ) + + $scope.path = "home" +]) \ No newline at end of file diff --git a/client/coffee/directives/service.coffee b/client/coffee/directives/service.coffee new file mode 100644 index 0000000..5e67f24 --- /dev/null +++ b/client/coffee/directives/service.coffee @@ -0,0 +1,31 @@ + +template = """ +
+
+
+ +
+
+ +
+""" + +directive = ($rootScope) -> + directive = + template: template + replace: true, + transclude: false, + restrict: 'E', + scope: + service: "=" + controller:["$scope", + ($scope, $timeout) -> + + ] + return directive + +directives.directive("babService", ['$rootScope', directive]) diff --git a/client/favicon.ico b/client/favicon.ico new file mode 100644 index 0000000..d828c12 Binary files /dev/null and b/client/favicon.ico differ diff --git a/client/images/bin.png b/client/images/bin.png new file mode 100644 index 0000000..3bcf5ce Binary files /dev/null and b/client/images/bin.png differ diff --git a/client/images/gopher.svg b/client/images/gopher.svg new file mode 100644 index 0000000..171ecec --- /dev/null +++ b/client/images/gopher.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/images/logo.svg b/client/images/logo.svg new file mode 100755 index 0000000..de3f45d --- /dev/null +++ b/client/images/logo.svg @@ -0,0 +1,20 @@ + + + Layer 1 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..f81c052 --- /dev/null +++ b/client/index.html @@ -0,0 +1,52 @@ + + + + + + + bab + + + + + + + + + + + +
+
+

Bab

+
+
+
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..ad059c5 --- /dev/null +++ b/client/package.json @@ -0,0 +1,11 @@ +{ + "devDependencies": { + "grunt": "^0.4.4", + "grunt-bower-task": "^0.3.4", + "grunt-contrib-connect": "^0.7.1", + "grunt-contrib-coffee": "^0.10.1", + "grunt-contrib-watch": "^0.6.1", + "grunt-concurrent": "^0.5.0", + "grunt-contrib-clean": "^0.5.0" + } +} diff --git a/client/partials/about.html b/client/partials/about.html new file mode 100644 index 0000000..aadd426 --- /dev/null +++ b/client/partials/about.html @@ -0,0 +1,25 @@ +
+
+

+ Thanks for using Bab. +

+

+ The source code of the application is available on + bitbucket. +

+
+
+ +
+
+ +
+ +
+ +
+
+

This program has been done with Go!

+
+ +
\ No newline at end of file diff --git a/client/partials/home.html b/client/partials/home.html new file mode 100644 index 0000000..91cc64d --- /dev/null +++ b/client/partials/home.html @@ -0,0 +1,10 @@ +
+
+
    +
  • + +
  • +
+
+
+ diff --git a/client/partials/login.html b/client/partials/login.html new file mode 100644 index 0000000..64aa0bd --- /dev/null +++ b/client/partials/login.html @@ -0,0 +1,27 @@ +
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
\ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..8879685 --- /dev/null +++ b/main.go @@ -0,0 +1,106 @@ +package main + +import ( + "encoding/json" + "fmt" + "github.com/gorilla/mux" + "io/ioutil" + "launchpad.net/goyaml" + "log" + "net/http" + "path" +) + +const helloworld string = ` + ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ +||s ||||e ||||r ||||v ||||i ||||n ||||g ||||: ||||8 ||||0 ||||0 ||||0 || +||__||||__||||__||||__||||__||||__||||__||||__||||__||||__||||__||||__|| +|/__\||/__\||/__\||/__\||/__\||/__\||/__\||/__\||/__\||/__\||/__\||/__\| + +` + +type WebAppServers []*WebAppServer + +func (this WebAppServers) Handle(w http.ResponseWriter, request *http.Request) { + for _, was := range this { + if was.Subdomain == mux.Vars(request)["subdomain"] { + was.Handle(w, request) + return + } + } + fmt.Fprint(w, "could not find a web application for ") +} + +func (this WebAppServers) Localize(domain string) WebAppServers { + fmt.Println(">>", domain) + ret := make(WebAppServers, len(this)) + for i, was := range this { + ret[i] = was.Localize(domain) + } + return ret +} + +var webAppServers WebAppServers +var port int = 8000 + +func init() { + var err error + webAppServers, err = loadWebAppServers() + if err != nil { + panic(err) + } +} + +func loadWebAppServers() (WebAppServers, error) { + data, err := ioutil.ReadFile("webapps.yml") + if err != nil { + return nil, err + } + return loadWebAppServersFromYamlFile(data) + +} + +func loadWebAppServersFromYamlFile(yamlFile []byte) (WebAppServers, error) { + ret := make(WebAppServers, 0, 10) + err := goyaml.Unmarshal(yamlFile, &ret) + return ret, err + +} + +func serveStatic(router *mux.Router) { + handler := func(w http.ResponseWriter, request *http.Request) { + vars := mux.Vars(request) + filepath := "/" + vars["path"] + w.Header().Set("Cache-Control", "public, max-age=43200") + http.ServeFile(w, request, path.Join("client/", filepath)) + } + router.HandleFunc("/{path:.*}", handler) +} + +func ServeWebApps(router *mux.Router) { + router.HandleFunc("/{path:.*}", webAppServers.Handle) +} + +func WebappHandler(w http.ResponseWriter, request *http.Request) { + w.Header().Set("Content-Type", "application/json") + encoder := json.NewEncoder(w) + encoder.Encode(webAppServers.Localize(request.Host)) +} + +func createMuxRouter() http.Handler { + r := mux.NewRouter() + ServeWebApps(r.Host(`{subdomain:\w+}.{domain:\w+}.{topleveldomain:[a-z]+}`).Subrouter()) + ServeWebApps(r.Host(`{subdomain:\w+}.localhost`).Subrouter()) + ServeWebApps(r.Host(`{subdomain:\w+}.\d{1-3}.\d{1-3}.\d{1-3}.\d{1-3}`).Subrouter()) + r.HandleFunc("/webapps", WebappHandler) + serveStatic(r.PathPrefix("/").Subrouter()) + return r +} + +func main() { + fmt.Print(helloworld) + address := fmt.Sprintf("0.0.0.0:%v", port) + if err := http.ListenAndServe(address, createMuxRouter()); err != nil { + log.Fatal(err) + } +} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..f913cba --- /dev/null +++ b/main_test.go @@ -0,0 +1,28 @@ +package main + +import ( + "testing" +) + +func TestLoadWebAppServersFromYamlFile(t *testing.T) { + yml := []byte(` +--- + +- + name: mongs + url: http://localhost:3333 + subdomain: mongs + image: images/mongo.png + +- + name: ipython + url: http://localhost:8888 + subdomain: ipython + image: images/ipython.png +`) + if was, err := loadWebAppServersFromYamlFile(yml); err != nil { + t.Error(err) + } else if len(was) != 2 { + t.Fail() + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..3e25ab5 --- /dev/null +++ b/readme.md @@ -0,0 +1,3 @@ +# Bab + +Bab is a gateway to your web applications. Run \ No newline at end of file diff --git a/webapps.yml b/webapps.yml new file mode 100644 index 0000000..d6e6851 --- /dev/null +++ b/webapps.yml @@ -0,0 +1,15 @@ +--- + +# This yaml file where your need to define your services. Look at the *http bin* +# example for fields one needs to give to define a service +# +# Before wishing to use *bab*, one needs to setup correctly its DNS server to points +# the subdomains to the correct ips. For localhost case, we recommend changing the +# /etc/hosts file to have a file similar to the one bellow +# 127.0.0.1 localhost httpbin.localhost + +- + name: http bin # service's name as it will be shown to the user + url: http://httpbin.org # service's url + subdomain: httpbin # subdomain where the service will be reachable + image: images/bin.png # service's imagae path relative to the client directory diff --git a/webappserver.go b/webappserver.go new file mode 100644 index 0000000..751e9f0 --- /dev/null +++ b/webappserver.go @@ -0,0 +1,95 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "regexp" + "strings" +) + +var _ = fmt.Print + +type WebAppServer struct { + Subdomain string `yaml:"subdomain"` + WebAppURL string `yaml:"url"` + ImageUrl string `yaml:"image"` + Name string `yaml:"name"` + Domain string `yaml:"default_domain,omitempty"` +} + +func (this *WebAppServer) Localize(domain string) *WebAppServer { + return &WebAppServer{this.Subdomain, this.WebAppURL, this.ImageUrl, this.Name, domain} +} + +type jsonWebAppServer struct { + Name string `json:"name"` + Url string `json:"url"` + Image string `json:"image"` +} + +func (this *WebAppServer) copyHeader(from, to http.Header) { + for key, val := range from { + to.Set(key, strings.Join(val, ";")) + } +} + +func (this *WebAppServer) GetWebAppBaseUrl() string { + return fmt.Sprintf("http://%s.%s", this.Subdomain, this.Domain) +} + +func (this *WebAppServer) MarshalJSON() ([]byte, error) { + ret := &jsonWebAppServer{this.Name, + this.GetWebAppBaseUrl(), + this.ImageUrl} + return json.Marshal(ret) +} + +func (this *WebAppServer) HandleGet(w http.ResponseWriter, request *http.Request) { + URL := this.WebAppURL + request.URL.Path + if request.URL.RawQuery != "" { + URL += "?" + request.URL.RawQuery + } + req, err := http.NewRequest(request.Method, URL, request.Body) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, "bab error :(", err.Error()) + return + } + this.copyHeader(request.Header, req.Header) + resp, err := new(http.Client).Do(req) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, "bab error :(\n", err.Error()) + return + } + this.copyHeader(resp.Header, w.Header()) + w.WriteHeader(resp.StatusCode) + defer resp.Body.Close() + io.Copy(w, resp.Body) +} + +func (this *WebAppServer) Handle(w http.ResponseWriter, request *http.Request) { + switch request.Method { + case "GET": + this.HandleGet(w, request) + case "POST": + this.HandleGet(w, request) + case "PUT": + this.HandleGet(w, request) + default: + w.WriteHeader(500) + fmt.Fprint(w, "method "+request.Method+" is not yet available") + } + +} + +func (this *WebAppServer) CanHandle(host string) bool { + r := regexp.MustCompile(`[:\.]`) + domain := r.Split(host, -1) + if len(domain) < 3 { + return false + } + return domain[len(domain)-3] == this.Subdomain +} diff --git a/webappserver_test.go b/webappserver_test.go new file mode 100644 index 0000000..e6fa73d --- /dev/null +++ b/webappserver_test.go @@ -0,0 +1,213 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +type TestServer struct { + Server *httptest.Server + Get func(w http.ResponseWriter, request *http.Request) + Post func(w http.ResponseWriter, request *http.Request) + T *testing.T +} + +func (this *TestServer) ServeHTTP(w http.ResponseWriter, request *http.Request) { + if request.Method == "GET" { + this.Get(w, request) + } else if request.Method == "POST" { + this.Post(w, request) + } else { + this.T.Error("cannot execute ", request.Method, " method") + } +} + +func (this *TestServer) HelloWorld(w http.ResponseWriter, request *http.Request) { + fmt.Fprint(w, "hello world\n") +} + +func (this *TestServer) ReturnPath(w http.ResponseWriter, request *http.Request) { + fmt.Fprint(w, request.URL.Path) +} + +func (this *TestServer) ReturnBogusHeader(w http.ResponseWriter, request *http.Request) { + fmt.Fprint(w, request.Header.Get("Bogus")) +} + +func (this *TestServer) ReturnBody(w http.ResponseWriter, request *http.Request) { + io.Copy(w, request.Body) + +} + +func (this *TestServer) SetBogusHeader(w http.ResponseWriter, request *http.Request) { + w.Header().Set("Bogus", "has been set on the server") + fmt.Fprint(w, request.Header.Get("Bogus")) +} + +func (this *TestServer) ReturnParamValue(w http.ResponseWriter, request *http.Request) { + if err := request.ParseForm(); err != nil { + this.T.Error(err) + return + } + fmt.Fprint(w, request.Form.Get("search")) +} + +func GetTestServer(t *testing.T) *TestServer { + ret := new(TestServer) + ret.T = t + server := httptest.NewServer(ret) + ret.Server = server + return ret +} + +func TestMarshalJSON(t *testing.T) { + wapp := &WebAppServer{"big", "http://bigtits.com", "img.png", "some name", "tits.com:8000"} + b, err := json.Marshal(wapp) + if err != nil { + t.Error(err) + } + if s := string(b); !strings.Contains(s, `"http://big.tits.com:8000"`) { + t.Fail() + } +} + +func TestCanHandle(t *testing.T) { + was := &WebAppServer{Subdomain: "foo"} + exps := map[string]bool{"foo.bigtits.com": true, + "foo.localhost:8000": true, + "bar.bigtits.com": false, + "bigtits.com": false} + for host, exp := range exps { + if was.CanHandle(host) != exp { + t.Error("error handeling ", host) + } + } +} + +func TestHandleGetHelloWorld(t *testing.T) { + ts := GetTestServer(t) + ts.Get = ts.HelloWorld + was := &WebAppServer{Subdomain: "foo", WebAppURL: ts.Server.URL} + w := httptest.NewRecorder() + req, err := http.NewRequest("GET", "http://foo.bigtits.com/natasha", nil) + if err != nil { + t.Error(err) + } + was.HandleGet(w, req) + if body := string(w.Body.Bytes()); body != "hello world\n" { + t.Error(body) + } +} + +func TestHandleGetPath(t *testing.T) { + ts := GetTestServer(t) + ts.Get = ts.ReturnPath + was := &WebAppServer{Subdomain: "foo", WebAppURL: ts.Server.URL} + w := httptest.NewRecorder() + req, err := http.NewRequest("GET", "http://foo.bigtits.com/natasha", nil) + if err != nil { + t.Error(err) + } + was.HandleGet(w, req) + if body := string(w.Body.Bytes()); body != "/natasha" { + t.Error(body) + } +} + +func TestHandleGetBogusHeader(t *testing.T) { + ts := GetTestServer(t) + ts.Get = ts.ReturnBogusHeader + was := &WebAppServer{Subdomain: "foo", WebAppURL: ts.Server.URL} + w := httptest.NewRecorder() + req, err := http.NewRequest("GET", "http://foo.bigtits.com/natasha", nil) + if err != nil { + t.Error(err) + } + req.Header.Set("Bogus", "something in the header!") + was.HandleGet(w, req) + if body := string(w.Body.Bytes()); body != "something in the header!" { + t.Error(body) + } +} + +func TestHandleGetSetBogusHeader(t *testing.T) { + ts := GetTestServer(t) + ts.Get = ts.SetBogusHeader + was := &WebAppServer{Subdomain: "foo", WebAppURL: ts.Server.URL} + w := httptest.NewRecorder() + req, err := http.NewRequest("GET", "http://foo.bigtits.com/natasha", nil) + if err != nil { + t.Error(err) + } + was.HandleGet(w, req) + if header := w.Header().Get("Bogus"); header != "has been set on the server" { + t.Error(header) + } +} + +func TestHandleGetReturnParamValue(t *testing.T) { + ts := GetTestServer(t) + ts.Get = ts.ReturnParamValue + was := &WebAppServer{Subdomain: "foo", WebAppURL: ts.Server.URL} + w := httptest.NewRecorder() + req, err := http.NewRequest("GET", "http://foo.bigtits.com/natasha?search=bigtits", nil) + if err != nil { + t.Error(err) + } + was.HandleGet(w, req) + if body := string(w.Body.Bytes()); body != "bigtits" { + t.Error(body) + } +} + +func TestHandlePostWithBody(t *testing.T) { + ts := GetTestServer(t) + ts.Post = ts.ReturnBody + was := &WebAppServer{Subdomain: "foo", WebAppURL: ts.Server.URL} + w := httptest.NewRecorder() + req, err := http.NewRequest("POST", "http://foo.bigtits.com/natasha", bytes.NewBuffer([]byte(`hello world in the body`))) + if err != nil { + t.Error(err) + } + was.Handle(w, req) + if body := string(w.Body.Bytes()); body != "hello world in the body" { + t.Error("wrong body :", body) + } +} + +func TestHandlePostBogusHeader(t *testing.T) { + ts := GetTestServer(t) + ts.Post = ts.SetBogusHeader + was := &WebAppServer{Subdomain: "foo", WebAppURL: ts.Server.URL} + w := httptest.NewRecorder() + req, err := http.NewRequest("POST", "http://foo.bigtits.com/natasha", nil) + if err != nil { + t.Error(err) + } + was.HandleGet(w, req) + if header := w.Header().Get("Bogus"); header != "has been set on the server" { + t.Error(header) + } +} + +func TestCanHandleNoService(t *testing.T) { + ts := GetTestServer(t) + + was := &WebAppServer{Subdomain: "foo", WebAppURL: ts.Server.URL} + ts.Server.Close() + w := httptest.NewRecorder() + req, err := http.NewRequest("GET", "http://foo.bigtits.com/natasha?search=bigtits", nil) + if err != nil { + t.Error(err) + } + was.HandleGet(w, req) + if w.Code != 500 { + t.Error("status should be 500, service not found") + } +}