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)
+ }
+ }
+}