diff --git a/conf.ini b/conf.ini new file mode 100644 index 0000000..1cfd8c7 --- /dev/null +++ b/conf.ini @@ -0,0 +1,7 @@ +[default] +cookiesecret = some secrete phrase +mapohome = /mapo/root/folder + +[googleoauth] +clientid = client_id +clientsecret = client_secret diff --git a/db/db.go b/db/db.go index 0c097cc..04b912d 100644 --- a/db/db.go +++ b/db/db.go @@ -22,3 +22,68 @@ Package db contains a data abstraction layer and underlying facilities to store entities in a database. */ package db + +import ( + "github.com/maponet/utils/log" + + "labix.org/v2/mgo" + "labix.org/v2/mgo/bson" +) + +// un oggetto globale che contiene una connessione attiva con la database. +var database *mgo.Database + +// TODO: definire una funzione che si occupa con la creazione e gestione della +// connessione verso un database. +func NewConnection(databaseName string) error { + log.Info("executing NewConnection function") + + session, err := mgo.Dial("localhost") + if err != nil { + return err + } + + database = session.DB(databaseName) + return nil + // connessione alla data base avvenne usando diversi livelli di autenticazione +} + +// Store salva nella database un singolo oggetto +func Store(data interface{}, table string) error { + + c := database.C(table) + + err := c.Insert(data) + + return err +} + +// RestoreOne riprende dalla database un singolo oggetto identificato da un id +func RestoreOne(data interface{}, filter bson.M, table string) error { + + c := database.C(table) + + err := c.Find(filter).One(data) + + return err +} + +// RestoreList riprende dalla database una lista (tutti) di oggetti, senza alcun filtro +func RestoreList(data interface{}, filter bson.M, table string) error { + + c := database.C(table) + + err := c.Find(filter).All(data) + + return err +} + +// Update aggiorna i valori di un oggetto nella database, identificato da un id +func Update(data interface{}, id string, table string) error { + + c := database.C(table) + + err := c.Update(bson.M{"_id": id}, data) + + return err +} diff --git a/log/.log.go.swo b/log/.log.go.swo deleted file mode 100644 index 31bbc07..0000000 Binary files a/log/.log.go.swo and /dev/null differ diff --git a/log/log.go b/log/log.go deleted file mode 100644 index b39a22f..0000000 --- a/log/log.go +++ /dev/null @@ -1,81 +0,0 @@ -/* -Copyright 2013 Petru Ciobanu, Francesco Paglia, Lorenzo Pierfederici - -This file is part of Mapo. - -Mapo is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 2 of the License, or -(at your option) any later version. - -Mapo is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mapo. If not, see . -*/ - -/* -Package log contains a simple multi-level logger. -*/ -package log - -import ( - "fmt" - "time" -) - -// Available log levels -const ( - ERROR = iota - INFO - DEBUG -) - -type logger struct { - level int -} - -var l logger - -// SetLevel sets the output level for the global logger -func SetLevel(level int) { - l.level = level -} - -func print(level int, format string, v ...interface{}) { - if level <= l.level { - var msgType string - - switch level { - case ERROR: - msgType = "ERROR" - case INFO: - msgType = "INFO" - case DEBUG: - msgType = "DEBUG" - } - - msg := fmt.Sprintf(format, v...) - t := time.Now().Format(time.RFC1123) - fmt.Printf("%s [%s]: %s\n", t, msgType, msg) - - } -} - -// Error logs a message at "ERROR" level -func Error(format string, v ...interface{}) { - print(ERROR, format, v...) -} - -// Info logs a message at "INFO" level -func Info(format string, v ...interface{}) { - print(INFO, format, v...) -} - -// Debug logs a message at "DEBUG" level -func Debug(format string, v ...interface{}) { - print(DEBUG, format, v...) -} diff --git a/mapo.go b/mapo.go index 5c2790b..336fbd5 100644 --- a/mapo.go +++ b/mapo.go @@ -20,32 +20,119 @@ along with Mapo. If not, see . package main import ( - "mapo/log" + "mapo/db" + "github.com/maponet/utils/log" + "github.com/maponet/utils/conf" + + "flag" + "net/http" + "os" + "os/signal" + "syscall" ) func main() { - // parse flags + + /* + parse flags + + In some situation we will pass path to configuration file as a command line + value. This meaning that for first off all we need to define and parse all flags. + The only flag that we required on this step is only conf flag ... But we + can't distribute code with same functionality along file or files. + */ + var logLevel = log.FlagLevel("log") + var confFilePath = flag.String("conf", "./conf.ini", "set path to configuration file") + flag.Parse() // load config and setup application - log.SetLevel(log.DEBUG) - log.Info("Setting log level to DEBUG") + err := conf.ParseConfigFile(*confFilePath) + if err != nil { + log.Error("%v", err) + return + } + + // setup configuration value passed as command line arguments + if len(*logLevel) > 0 { + conf.GlobalConfiguration.AddOption("default", "loglevel", *logLevel) + } + + // setup application + + // set log level + value, _ := conf.GlobalConfiguration.GetString("default", "loglevel") + if err := log.SetLevelString(value); err != nil { + log.Error("%v", err) + return + } log.Info("Starting application") - // register with supervisor - log.Info("Joining supervisor") - // init db log.Info("Initializing db") + /* + in questa configurazione, connessione alla database viene attivata in un + oggetto definito globalmente al interno del modulo db. + L'idea originale per Mapo è di creare un oggetto che contenga la + connessione attiva e passare questo aggetto a tutte le funzione che ne + hanno bisogno di fare una richiesta alla database. + + Passare l'oggetto database da una funzione ad altra, potrebbe + significare, creare una catena dalla prima funzione all'ultima. Che + avvolte non fa niente altro che aumentare il numero dei parametri passati + da una funzione ad altra. Per esempio, la connessione al database si usa + nel modulo objectspace che viene chiamato dal modulo admin che al suo tempo + viene chiamato da main. Inutile passare questo oggetto al modulo admin, + visto che li lui non serve. + + NOTA: accesso ai oggetti globali deve essere in qualche modo sincronizzato + per evitare i problemi di inconsistenza. + + NOTA: le osservazioni dimostrano che avendo una connessione attiva alla + database che poi viene riutilizzata, diminuisce considerevolmente i tempi di + interrogazione. + */ + err = db.NewConnection("mapo") + if err != nil { + log.Error("%v", err) + return + } + // load addons log.Info("Loading addons") + // al momento del spegnimento dell'applicazione potremo trovarci con delle + // connessione attive dal parte del cliente. Il handler personalizzato usato + // qui, ci permette di dire al server di spegnersi ma prima deve aspettare + // che tutte le richieste siano processate e la connessione chiusa. + // + // Oltre al spegnimento sicuro il ServeMux permette di registra dei nuovi + // handler usando come descrizione anche il metodo http tipo GET o POST. + muxer := NewServeMux() + + // prepare server + server := &http.Server{ + Addr: ":8081", + Handler: muxer, + } + + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGINT) + + // aviamo in una nuova gorutine la funzione che ascolterà per il segnale di + // spegnimento del server + go muxer.getSignalAndClose(c) + // register handlers log.Info("Registering handlers") + // register with supervisor + log.Info("Joining supervisor") + // start server - log.Info("Accepting requests") + log.Info("Listening for requests") + log.Info("close server with message: %v", server.ListenAndServe()) // inform supervisor that we are up @@ -64,7 +151,6 @@ func main() { // return result to user - // close on signal log.Info("Closing application") } diff --git a/mux.go b/mux.go index ff97c40..ac85da4 100644 --- a/mux.go +++ b/mux.go @@ -18,3 +18,212 @@ along with Mapo. If not, see . */ package main + +import ( + "github.com/maponet/utils/log" + + "os" + "sync" + "time" + "regexp" + "strings" + "net/http" +) + +/* +ServeMux, nasce dalla necessita di registrare dei handler differenziati anche +dal metodo http usato durante la richiesta dal parte del utente. Cosi lo stesso +url usato con il metodo POST ha un funzionamento diverso da una richiesta dove +si usa il metodo GET. + +Un altra possibilità che ci offre questo handler personalizzato è di poter +interrompere il server usando la combinazione dei tasti CTRL+C +*/ +type ServeMux struct { + + // lista dei handler registrati, con o senza autenticazione + m map[string]Handler + mVars map[string]map[int]string + + // il numero delle connessione attive in questo momento + current_connections int + lock sync.Mutex + + // il server è o no in fase di chiusura + closing bool +} + +func (mux *ServeMux) HandleFunc(method, path string, handle func(http.ResponseWriter, *http.Request)) { + handlerFunc := new(http.HandlerFunc) + *handlerFunc = handle + + pattern := createPattern(method, path) + + mux.m[pattern] = handlerFunc + mux.mVars[pattern] = createUrlVars(path) +} + +func (mux *ServeMux) Handle(method, path string, handler Handler) { + pattern := createPattern(method, path) + mux.m[pattern] = handler + mux.mVars[pattern] = createUrlVars(path) +} + +/* +createPattern, crea l'espressione regulare che si usa più tardi per trovare +il handler corretto per il path/risorsa richiesta. +*/ +func createPattern(method, path string) string { + pattern := "(?i)^" + + if method != "" { + pattern = pattern + method + ":/" + } else { + pattern = pattern + "(GET|POST)" + ":/" + } + + if len(path) > 1 { + pathSlice := strings.Split(path[1:], "/") + for _, v := range(pathSlice) { + if v[0] == '{' { + pattern = pattern + "[0-9a-z_\\ \\.\\+\\-]*/" + } else { + pattern = pattern + v + "/" + } + } + } + pattern = pattern + "$" + return pattern +} + +/* +createUrlVars, mappa le variabili inserite del url inserite al momento della +registrazione del handler. Le variabili sono segnati con le parentesi graffe +al interno delle quali si trova il nome della variabile. Questa mappa sarà usata +più tardi per passare i dati a forma di copie (chiave:valore) ai handler. +*/ +func createUrlVars(path string) map[int]string { + vlist := strings.Split(path, "/") + + data := make(map[int]string,0) + + for i, v := range(vlist) { + if len(v) < 3 { + continue + } + if v[0] == '{' && v[len(v)-1] == '}' { + data[i] = v[1:len(v)-1] + } + } + + return data +} + +/* +match, è usata per identificare quale dei handler corrisponde per un certo url. +Questa funzione fa utilizzo dei pattern (espressioni regolari). +*/ +func (mux *ServeMux) match(r *http.Request) (Handler, string) { + method := r.Method + url := r.URL.Path + + if url[len(url)-1] != '/' { + url = url + "/" + } + + var handler Handler + var pattern string + + for k, v := range(mux.m) { + matching, _ := regexp.MatchString(k, method + ":" + url) + if matching { + handler = v + pattern = k + break + } + } + + if handler != nil { + return handler, pattern + } + return http.NotFoundHandler(), "" +} + +/* +NewServeMux, restituisce un nuovo miltiplixier personalizzato. +*/ +func NewServeMux() *ServeMux { + mux := new(ServeMux) + mux.m = make(map[string]Handler, 0) + mux.mVars = make(map[string]map[int]string, 0) + + return mux +} + +/* +Handler, è un interfaccia che come funzionalità e struttura non è diversa dal +handler originale del modulo http di go. +TODO: Probabilmente è più corretto usare il http.Handler, resta da verificare. +*/ +type Handler interface { + ServeHTTP(http.ResponseWriter, *http.Request) +} + +// ServeHTTP e la funzione che vine eseguita come gorutine ogni volta che +// si deve processare qualche richiesta. Questa funzione soltanto si assicura +// che venga incrementato o decrementato il numero delle connessione attive e +// avvierà la funzione RequestHandler che processerà la richiesta del cliente. +// Comunque, il server http viene interrotto in maniera brutta ma senza alcun +// rischio. TODO: approfondire questa feature se servirà. +func (mux *ServeMux) ServeHTTP(out http.ResponseWriter, in *http.Request) { + if !mux.closing { + start := time.Now() + defer func() { + log.Info("time: %v for %s", time.Since(start), in.URL.Path) + }() + + mux.lock.Lock() + mux.current_connections++ + mux.lock.Unlock() + + defer func() { + mux.lock.Lock() + mux.current_connections-- + mux.lock.Unlock() + }() + + handle, pattern := mux.match(in) + if len(pattern) > 0 { + in.ParseMultipartForm(0) + urlValues := strings.Split(in.URL.Path, "/") + for k, v := range(mux.mVars[pattern]) { + in.Form[v] = []string{urlValues[k]} + } + } + handle.ServeHTTP(out, in) + } +} + +// se viene richiesto che l'applicazione si deve chiudere, in questo momento si +// parla del commando CTRL+C dal terminale, potremmo corrompere i dati a colpa +// del'interruzione in maniera incorretta delle richieste in corso. La presente +// Funzione sta in ascolto per il segnale SIGINT dopo di che si assicura che il +// server venga chiuso non appena le connessione attive saranno zero. +func (mux *ServeMux) getSignalAndClose(c chan os.Signal) { + + _ = <-c + log.Info("closing ...") + mux.closing = true + + // TODO: send notification to load balancing that this node is unavailable + + for { + if mux.current_connections == 0 { + log.Info("bye ... :)") + os.Exit(1) + } else { + log.Info("waiting for %d opened connections", mux.current_connections) + time.Sleep(500 * time.Millisecond) + } + } +}