diff --git a/BinaryAssets/Access.go b/BinaryAssets/Access.go index bee06d9..a02f0f2 100644 --- a/BinaryAssets/Access.go +++ b/BinaryAssets/Access.go @@ -4,6 +4,7 @@ import ( "github.com/SommerEngineering/Ocean/BinaryAssets/SourceCodePro" ) +// Reads the content for a file. func GetData(filename string) (data []byte) { if obj, err := SourceCodePro.Asset(filename); err != nil { return diff --git a/BinaryAssets/Handlers.go b/BinaryAssets/Handlers.go index 834c43e..d3ec737 100644 --- a/BinaryAssets/Handlers.go +++ b/BinaryAssets/Handlers.go @@ -10,8 +10,10 @@ import ( "strings" ) +// Handler to access the binary assets from the web. func HandlerBinaryAssets(response http.ResponseWriter, request *http.Request) { + // Case: The server is going down. if Shutdown.IsDown() { http.NotFound(response, request) return @@ -31,12 +33,14 @@ func HandlerBinaryAssets(response http.ResponseWriter, request *http.Request) { fileType = mimeType.MimeType } + // Case: No MIME type determined? if fileType == `` { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameNOTFOUND, `The mime type is unknown.`, path) http.NotFound(response, request) return } + // Read the content: contentData := GetData(path) if contentData == nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `The desired file was not found.`, path) @@ -44,6 +48,7 @@ func HandlerBinaryAssets(response http.ResponseWriter, request *http.Request) { return } + // Write the meta data and the content to the client: fileLenText := fmt.Sprintf(`%d`, len(contentData)) response.Header().Add(`Content-Length`, fileLenText) response.Header().Add(`Content-Type`, fileType) diff --git a/BinaryAssets/Variables.go b/BinaryAssets/Variables.go index bc329ed..60646f4 100644 --- a/BinaryAssets/Variables.go +++ b/BinaryAssets/Variables.go @@ -5,5 +5,5 @@ import ( ) var ( - senderName LM.Sender = `System::BinaryAssets` + senderName LM.Sender = `System::BinaryAssets` // This is the name for logging event from this package ) diff --git a/Configuration/Init.go b/Configuration/Init.go index 9682b9a..545cd71 100644 --- a/Configuration/Init.go +++ b/Configuration/Init.go @@ -1,5 +1,6 @@ package Configuration +// This is the init function for this package. func init() { readConfiguration() isInit = true diff --git a/Configuration/ReadConfiguration.go b/Configuration/ReadConfiguration.go index 7a9ede6..88ebd20 100644 --- a/Configuration/ReadConfiguration.go +++ b/Configuration/ReadConfiguration.go @@ -8,8 +8,8 @@ import ( "path/filepath" ) +// Function to read the configuration file. func readConfiguration() { - if isInit { Log.LogFull(senderName, Meta.CategorySYSTEM, Meta.LevelWARN, Meta.SeverityNone, Meta.ImpactNone, Meta.MessageNameINIT, `The configuration package is already init!`) return @@ -17,13 +17,14 @@ func readConfiguration() { Log.LogShort(senderName, Meta.CategorySYSTEM, Meta.LevelINFO, Meta.MessageNameCONFIGURATION, `Init of configuration starting.`) } + // Access to the working directory? currentDir, dirError := os.Getwd() - if dirError != nil { panic(`Was not able to read the working directory: ` + dirError.Error()) return } + // Access to the configuration file? currentPath := filepath.Join(currentDir, filename) if _, errFile := os.Stat(currentPath); errFile != nil { if os.IsNotExist(errFile) { @@ -33,6 +34,7 @@ func readConfiguration() { } } + // Open the file: file, fileError := os.Open(currentPath) defer file.Close() @@ -41,6 +43,7 @@ func readConfiguration() { return } + // Try to decode / parse the file: decoder := json.NewDecoder(file) decError := decoder.Decode(&configuration) diff --git a/Configuration/Variables.go b/Configuration/Variables.go index 94c0ba6..7e1b794 100644 --- a/Configuration/Variables.go +++ b/Configuration/Variables.go @@ -6,8 +6,8 @@ import ( ) var ( - filename = "configuration.json" // Where is the configuration located? - configuration Meta.Configuration = Meta.Configuration{} // The loaded configuration - isInit = false // Is the configuration loaded? - senderName LM.Sender = `System::Configuration` + filename = "configuration.json" // Where is the configuration located? + configuration Meta.Configuration = Meta.Configuration{} // The loaded configuration + isInit = false // Is the configuration loaded? + senderName LM.Sender = `System::Configuration` // This is the name for logging event from this package ) diff --git a/ConfigurationDB/CheckConfiguration.go b/ConfigurationDB/CheckConfiguration.go index 55d6c2b..d23f046 100644 --- a/ConfigurationDB/CheckConfiguration.go +++ b/ConfigurationDB/CheckConfiguration.go @@ -6,6 +6,7 @@ import ( "gopkg.in/mgo.v2/bson" ) +// Internal function to check the system configuration. func checkConfiguration() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameDATABASE, `Check now the configuration database.`) defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameDATABASE, `Done checking the configuration database.`) @@ -53,15 +54,14 @@ func checkConfiguration() { Disallow:`) } -/* -Use this function to ensure that the database contains at least a default value for the configuration. -*/ +// Use this function to ensure that the database contains at least a default value for the configuration. func CheckSingleConfigurationPresentsAndAddIfMissing(name, value string) { if !checkSingleConfigurationPresents(name) { addSingleConfiguration(name, value) } } +// Check if a configuration value is present. func checkSingleConfigurationPresents(name string) (result bool) { selection := bson.D{{"Name", name}} count, _ := collection.Find(selection).Count() @@ -69,6 +69,7 @@ func checkSingleConfigurationPresents(name string) (result bool) { return count > 0 } +// Adds a configuration value. func addSingleConfiguration(name, value string) { entry := ConfigurationDBEntry{} entry.Name = name diff --git a/ConfigurationDB/Init.go b/ConfigurationDB/Init.go index baf74ac..7d595b4 100644 --- a/ConfigurationDB/Init.go +++ b/ConfigurationDB/Init.go @@ -7,8 +7,8 @@ import ( "gopkg.in/mgo.v2" ) +// The init function for this package. func init() { - config := Configuration.Read() // Connect to MongoDB: @@ -40,6 +40,8 @@ func init() { // Take care about the index: collection.EnsureIndexKey(`Name`) + // Check the system configuration: checkConfiguration() + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameDATABASE, `The configuration database is now ready.`) } diff --git a/ConfigurationDB/Read.go b/ConfigurationDB/Read.go index d23c62e..f312758 100644 --- a/ConfigurationDB/Read.go +++ b/ConfigurationDB/Read.go @@ -6,9 +6,7 @@ import ( "gopkg.in/mgo.v2/bson" ) -/* -This function reads the current configuration value. -*/ +// This function reads the current configuration value. func Read(name string) (value string) { if name == `` { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `Was not able to read a configuration out of the database.`, `The given name was nil!`) diff --git a/ConfigurationDB/Scheme.go b/ConfigurationDB/Scheme.go index fa02b6a..d551fd1 100644 --- a/ConfigurationDB/Scheme.go +++ b/ConfigurationDB/Scheme.go @@ -1,5 +1,6 @@ package ConfigurationDB +// The type for a configuration entry. type ConfigurationDBEntry struct { Name string `bson:"Name"` Value string `bson:"Value"` diff --git a/ConfigurationDB/Shutdown.go b/ConfigurationDB/Shutdown.go index 3909a32..bd50f16 100644 --- a/ConfigurationDB/Shutdown.go +++ b/ConfigurationDB/Shutdown.go @@ -5,15 +5,11 @@ import ( LM "github.com/SommerEngineering/Ocean/Log/Meta" ) -/* -Do not use this type by your own! It is a Ocean internal type to provide a shutdown function for the configuration database. -*/ +// The type for the shutdown function. type ShutdownFunction struct { } -/* -If the Ocean server is shutting down, this function is called to close the database. -*/ +// If the Ocean server is shutting down, this function is called to close the database. func (a ShutdownFunction) Shutdown() { Log.LogShort(senderName, LM.CategoryAPP, LM.LevelWARN, LM.MessageNameSHUTDOWN, `Close now the configuration database connection.`) db.Logout() diff --git a/ConfigurationDB/Variables.go b/ConfigurationDB/Variables.go index f2b550d..a45a838 100644 --- a/ConfigurationDB/Variables.go +++ b/ConfigurationDB/Variables.go @@ -7,9 +7,9 @@ import ( ) var ( - session *mgo.Session = nil - db *mgo.Database = nil - collection *mgo.Collection = nil - config Meta.Configuration = Meta.Configuration{} - senderName LM.Sender = `System::ConfigurationDB` + session *mgo.Session = nil // The database session + db *mgo.Database = nil // The database + collection *mgo.Collection = nil // The database collection + config Meta.Configuration = Meta.Configuration{} // The configuration file's data + senderName LM.Sender = `System::ConfigurationDB` // This is the name for logging event from this package ) diff --git a/ConfigurationDB/Write.go b/ConfigurationDB/Write.go index 6bc9590..b0b3b19 100644 --- a/ConfigurationDB/Write.go +++ b/ConfigurationDB/Write.go @@ -6,15 +6,14 @@ import ( "gopkg.in/mgo.v2/bson" ) -/* -This function writes the configuration value. -*/ +// This function writes the configuration value. func Write(name, value string) { if name == `` { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `Was not able to write a configuration to the database.`, `The given name was nil!`) return } + // Read the current value: result := ConfigurationDBEntry{} if errFind := collection.Find(bson.D{{"Name", name}}).One(&result); errFind != nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `Was not able to write a configuration to the database.`, `Error while find.`, errFind.Error()) @@ -22,6 +21,8 @@ func Write(name, value string) { } result.Value = value + + // Update the database: collection.Update(bson.D{{"Name", name}}, result) return } diff --git a/CustomerDB/AccessDB.go b/CustomerDB/AccessDB.go index 93035b4..06b743c 100644 --- a/CustomerDB/AccessDB.go +++ b/CustomerDB/AccessDB.go @@ -4,25 +4,19 @@ import ( "gopkg.in/mgo.v2" ) -/* -Get the database instance of the MGo Mongo driver. -*/ +// Get the database instance of the mgo MongoDB driver. func DB() (session *mgo.Session, database *mgo.Database) { session = mainSession.Copy() database = session.DB(databaseDB) database.Login(databaseUsername, databasePassword) - return } -/* -Get directly the GridFS instance of the Mgo Mongo driver. -*/ +// Get directly the GridFS instance of the mgo MongoDB driver. func GridFS() (session *mgo.Session, filesystem *mgo.GridFS) { session = mainSession.Copy() database := session.DB(databaseDB) database.Login(databaseUsername, databasePassword) filesystem = database.GridFS(`fs`) - return } diff --git a/CustomerDB/Init.go b/CustomerDB/Init.go index b139299..6f63b18 100644 --- a/CustomerDB/Init.go +++ b/CustomerDB/Init.go @@ -7,10 +7,11 @@ import ( "gopkg.in/mgo.v2" ) +// The init function for this package. func init() { - Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameDATABASE, `Init the customer database.`) + // Read the configuration values: databaseHost := ConfigurationDB.Read(`CustomerDBHost`) databaseDB = ConfigurationDB.Read(`CustomerDBDatabase`) databaseUsername = ConfigurationDB.Read(`CustomerDBUsername`) diff --git a/CustomerDB/Shutdown.go b/CustomerDB/Shutdown.go index 2ca5e49..937f599 100644 --- a/CustomerDB/Shutdown.go +++ b/CustomerDB/Shutdown.go @@ -5,15 +5,11 @@ import ( LM "github.com/SommerEngineering/Ocean/Log/Meta" ) -/* -Please do not use this type. It is an internal type of Ocean to provide a shutdown function! -*/ +// The shutdown type. type ShutdownFunction struct { } -/* -This function is called if the Ocean server is shutting down. -*/ +// The shutdown function for this package. func (a ShutdownFunction) Shutdown() { Log.LogShort(senderName, LM.CategoryAPP, LM.LevelWARN, LM.MessageNameSHUTDOWN, `Close now the customer database connection.`) } diff --git a/CustomerDB/Variables.go b/CustomerDB/Variables.go index f43bd41..76ae12e 100644 --- a/CustomerDB/Variables.go +++ b/CustomerDB/Variables.go @@ -6,9 +6,9 @@ import ( ) var ( - mainSession *mgo.Session = nil - senderName LM.Sender = `System::CustomerDB` - databaseUsername string = `` - databasePassword string = `` - databaseDB string = `` + mainSession *mgo.Session = nil // The session for the customer database + senderName LM.Sender = `System::CustomerDB` // This is the name for logging event from this package + databaseUsername string = `` // The user's name + databasePassword string = `` // The user's password + databaseDB string = `` // The database ) diff --git a/Handlers/AddHandler.go b/Handlers/AddHandler.go index 3598752..9eb9754 100644 --- a/Handlers/AddHandler.go +++ b/Handlers/AddHandler.go @@ -4,10 +4,12 @@ import ( "net/http" ) +// Function to add a new public handler. func AddPublicHandler(pattern string, handler func(http.ResponseWriter, *http.Request)) { muxPublic.HandleFunc(pattern, handler) } +// Function to add a new private handler. func AddAdminHandler(pattern string, handler func(http.ResponseWriter, *http.Request)) { muxAdmin.HandleFunc(pattern, handler) } diff --git a/Handlers/GetMuxes.go b/Handlers/GetMuxes.go index b2838e7..3ee3ced 100644 --- a/Handlers/GetMuxes.go +++ b/Handlers/GetMuxes.go @@ -4,11 +4,13 @@ import ( "net/http" ) +// Returns the muxer for the public web server. func GetPublicMux() (mux *http.ServeMux) { mux = muxPublic return } +// Returns the muxer for the private web server. func GetAdminMux() (mux *http.ServeMux) { mux = muxAdmin return diff --git a/Handlers/Init.go b/Handlers/Init.go index a19d3c9..270cd59 100644 --- a/Handlers/Init.go +++ b/Handlers/Init.go @@ -1,5 +1,5 @@ package Handlers +// The init function for this package. func init() { - } diff --git a/Handlers/Variables.go b/Handlers/Variables.go index 37dee9d..6cbc580 100644 --- a/Handlers/Variables.go +++ b/Handlers/Variables.go @@ -6,7 +6,7 @@ import ( ) var ( - senderName LM.Sender = `System::Handlers` - muxPublic *http.ServeMux = http.NewServeMux() - muxAdmin *http.ServeMux = http.NewServeMux() + senderName LM.Sender = `System::Handlers` // This is the name for logging event from this package + muxPublic *http.ServeMux = http.NewServeMux() // The muxer for the public web server + muxAdmin *http.ServeMux = http.NewServeMux() // The muxer for the private web server ) diff --git a/ICCC/ListenerCache.go b/ICCC/CacheTimerLogic.go similarity index 69% rename from ICCC/ListenerCache.go rename to ICCC/CacheTimerLogic.go index 81c3501..3512f4b 100644 --- a/ICCC/ListenerCache.go +++ b/ICCC/CacheTimerLogic.go @@ -1,72 +1,40 @@ -package ICCC - -import ( - "fmt" - "github.com/SommerEngineering/Ocean/ICCC/Scheme" - "github.com/SommerEngineering/Ocean/Log" - LM "github.com/SommerEngineering/Ocean/Log/Meta" - "github.com/SommerEngineering/Ocean/Shutdown" - "gopkg.in/mgo.v2/bson" - "time" -) - -func InitCacheNow() { - startCacheTimerLock.Lock() - defer startCacheTimerLock.Unlock() - - if cacheTimerRunning { - return - } - - cacheTimerLogic(false) -} - -func StartCacheTimer() { - initCacheTimer() -} - -func initCacheTimer() { - startCacheTimerLock.Lock() - defer startCacheTimerLock.Unlock() - - if cacheTimerRunning { - return - } else { - cacheTimerRunning = true - } - - go func() { - for { - cacheTimerLogic(true) - } - }() -} - -func cacheTimerLogic(waiting bool) { - if Shutdown.IsDown() { - return - } - - lastCount := cacheListenerDatabase.Len() - selection := bson.D{{`IsActive`, true}} - entriesIterator := collectionListener.Find(selection).Iter() - entry := Scheme.Listener{} - - cacheListenerDatabaseLock.Lock() - cacheListenerDatabase.Init() - for entriesIterator.Next(&entry) { - cacheListenerDatabase.PushBack(entry) - } - - cacheListenerDatabaseLock.Unlock() - Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameEXECUTE, `The listener cache was refreshed with the values from the database.`, fmt.Sprintf(`last count=%d`, lastCount), fmt.Sprintf(`new count=%d`, cacheListenerDatabase.Len())) - - if waiting { - nextDuration := time.Duration(5) * time.Minute - if cacheListenerDatabase.Len() == 0 { - nextDuration = time.Duration(10) * time.Second - } - - time.Sleep(nextDuration) - } -} +package ICCC + +import ( + "fmt" + "github.com/SommerEngineering/Ocean/ICCC/Scheme" + "github.com/SommerEngineering/Ocean/Log" + LM "github.com/SommerEngineering/Ocean/Log/Meta" + "github.com/SommerEngineering/Ocean/Shutdown" + "gopkg.in/mgo.v2/bson" + "time" +) + +func cacheTimerLogic(waiting bool) { + if Shutdown.IsDown() { + return + } + + lastCount := cacheListenerDatabase.Len() + selection := bson.D{{`IsActive`, true}} + entriesIterator := collectionListener.Find(selection).Iter() + entry := Scheme.Listener{} + + cacheListenerDatabaseLock.Lock() + cacheListenerDatabase.Init() + for entriesIterator.Next(&entry) { + cacheListenerDatabase.PushBack(entry) + } + + cacheListenerDatabaseLock.Unlock() + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameEXECUTE, `The listener cache was refreshed with the values from the database.`, fmt.Sprintf(`last count=%d`, lastCount), fmt.Sprintf(`new count=%d`, cacheListenerDatabase.Len())) + + if waiting { + nextDuration := time.Duration(5) * time.Minute + if cacheListenerDatabase.Len() == 0 { + nextDuration = time.Duration(10) * time.Second + } + + time.Sleep(nextDuration) + } +} diff --git a/ICCC/Data2Message.go b/ICCC/Data2Message.go index ffc831f..56d3bbc 100644 --- a/ICCC/Data2Message.go +++ b/ICCC/Data2Message.go @@ -6,6 +6,7 @@ import ( "strconv" ) +// Function to convert the HTTP data back to a message. func Data2Message(target interface{}, data map[string][]string) (channel, command string, obj interface{}) { if data == nil || len(data) == 0 { channel = `` @@ -14,12 +15,15 @@ func Data2Message(target interface{}, data map[string][]string) (channel, comman return } + // Use reflection for the target type: element := reflect.ValueOf(target) element = element.Elem() elementType := element.Type() channel = data[`channel`][0] command = data[`command`][0] + + // Use the order of the destination type's fields: for i := 0; i < element.NumField(); i++ { field := element.Field(i) switch field.Kind().String() { @@ -53,6 +57,7 @@ func Data2Message(target interface{}, data map[string][]string) (channel, comman v, _ := strconv.ParseUint(mapValue, 16, 8) field.SetUint(v) + // Case: Arrays... case `slice`: sliceInterface := field.Interface() sliceKind := reflect.ValueOf(sliceInterface).Type().String() diff --git a/ICCC/Doc.go b/ICCC/Doc.go index 940b5e3..76578e9 100644 --- a/ICCC/Doc.go +++ b/ICCC/Doc.go @@ -1,21 +1,15 @@ /* -This is the "[I]nter-[C]omponent [C]ommunication [C]hannel". It is a minimal -messaging service to connect different servers or even different parts of -huge systems across programming languages. +This is the "[I]nter-[C]omponent [C]ommunication [C]hannel". It is a minimal messaging service to connect different servers or even different parts of huge systems across programming languages. -The basis idea is to create such messaging service on top of HTTP, because -every programming language is able to process HTTP. Therefore, all messages -are transformed to HTTP form values (with URL encoding). +The basis idea is to create such messaging service on top of HTTP, because every programming language is able to process HTTP. Therefore, all messages are transformed to HTTP form values (with URL encoding). -To be able to marshal / parse the data back to objects, some additional -information is added: +To be able to marshal / parse the data back to objects, some additional information is added: Example 01: name=str:Surname value=Sommer -The HTTP form name is 'str:Surname' and the value is 'Sommer'. The 'str' is -the indicator for the data type, in this case it is a string. +The HTTP form name is 'str:Surname' and the value is 'Sommer'. The 'str' is the indicator for the data type, in this case it is a string. Known data types are: * str := string @@ -48,7 +42,9 @@ channel=CHANNEL [any count of data tuples] InternalCommPassword=[configured communication password e.g. an UUID etc.] -If you want to build a distributed system across the Internet, please use e.g. SSH tunnels -to keep things secret. +If you want to build a distributed system across the Internet, please use e.g. SSH tunnels to keep things secret. + +Constrains to the environment: +The web server cannot reorder the fields of the request or response. The order of fields at the data object (message) must correspond with the order of fields inside the HTTP message. Therefore, a reorder is not possible at the moment. */ package ICCC diff --git a/ICCC/HTTPConnector.go b/ICCC/HTTPConnector.go index 866990a..aa35b58 100644 --- a/ICCC/HTTPConnector.go +++ b/ICCC/HTTPConnector.go @@ -8,36 +8,49 @@ import ( "net/http" ) +// The HTTP handler for ICCC. func ICCCHandler(response http.ResponseWriter, request *http.Request) { + + // Cannot parse the form? if errParse := request.ParseForm(); errParse != nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameNETWORK, `Was not able to parse the HTTP form data from an ICCC message!`) http.NotFound(response, request) return } + // Read the data out of the request: messageData := map[string][]string(request.PostForm) + // The data must contain at least three fields (command, channel & communication password) if len(messageData) < 3 { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameNETWORK, `The ICCC message contains not enough data: At least the channel, command and password is required!`) http.NotFound(response, request) return } + // Read the meta data: channel := messageData[`channel`][0] command := messageData[`command`][0] password := messageData[`InternalCommPassword`][0] + // Check the password: if password != Tools.InternalCommPassword() { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityCritical, LM.ImpactNone, LM.MessageNamePASSWORD, `Received a ICCC message with wrong password!`, request.RemoteAddr) http.NotFound(response, request) return } + // Build the key for the mapping of the listener cache: key := fmt.Sprintf(`%s::%s`, channel, command) + + // Get the matching listener listener := listeners[key] + if listener == nil { + // Case: No such listener Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `Was not able to find the correct listener for these ICCC message.`, `channel=`+channel, `command`+command, `hostname=`+Tools.ThisHostname()) } else { + // Case: Everything is fine => deliver the message listener(messageData) } } diff --git a/ICCC/Init.go b/ICCC/Init.go index f911866..e525be4 100644 --- a/ICCC/Init.go +++ b/ICCC/Init.go @@ -7,16 +7,23 @@ import ( "github.com/SommerEngineering/Ocean/Tools" ) +// Init this package. func init() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Start init of ICCC.`) defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Done init ICCC.`) + // Create the list as cache for all global listener (not only listener from this server): cacheListenerDatabase = list.New() + + // Create a mapping as cache for all local listener end-points (functions): listeners = make(map[string]func(data map[string][]string)) // Using the local IP address: correctAddressWithPort = Tools.LocalIPAddressAndPort() + // Init the database: initDB() + + // Register this server to the listener (if not present): registerHost2Database() } diff --git a/ICCC/InitCacheNow.go b/ICCC/InitCacheNow.go new file mode 100644 index 0000000..d643475 --- /dev/null +++ b/ICCC/InitCacheNow.go @@ -0,0 +1,13 @@ +package ICCC + +// Starts the timer cache once and exit it after (no thread, no endless loop). +func InitCacheNow() { + startCacheTimerLock.Lock() + defer startCacheTimerLock.Unlock() + + if cacheTimerRunning { + return + } + + cacheTimerLogic(false) +} diff --git a/ICCC/InitCacheTimer.go b/ICCC/InitCacheTimer.go new file mode 100644 index 0000000..1f3e9cc --- /dev/null +++ b/ICCC/InitCacheTimer.go @@ -0,0 +1,21 @@ +package ICCC + +// Setup and starts the cache timer. +func initCacheTimer() { + startCacheTimerLock.Lock() + defer startCacheTimerLock.Unlock() + + if cacheTimerRunning { + return + } else { + cacheTimerRunning = true + } + + // Start another thread with the timer logic: + go func() { + // Endless loop: + for { + cacheTimerLogic(true) + } + }() +} diff --git a/ICCC/InitDB.go b/ICCC/InitDB.go index 3fcc623..98dd9f6 100644 --- a/ICCC/InitDB.go +++ b/ICCC/InitDB.go @@ -7,6 +7,7 @@ import ( "gopkg.in/mgo.v2" ) +// Init the database. func initDB() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Start init of the ICCC collections.`) defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Done init the ICCC collection.`) @@ -14,6 +15,7 @@ func initDB() { // Get the database: dbSession, db = CustomerDB.DB() + // Case: Error? if db == nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to get the customer database.`) return @@ -23,7 +25,9 @@ func initDB() { collectionListener = db.C(`ICCCListener`) collectionHosts = db.C(`ICCCHosts`) + // // Take care about the indexes for ICCCListener: + // collectionListener.EnsureIndexKey(`Command`) collectionListener.EnsureIndexKey(`Command`, `IsActive`) @@ -45,7 +49,9 @@ func initDB() { indexName1.Unique = true collectionListener.EnsureIndex(indexName1) + // // Index for hosts: + // collectionHosts.EnsureIndexKey(`Hostname`, `IPAddressPort`) indexName2 := mgo.Index{} diff --git a/ICCC/Message2Data.go b/ICCC/Message2Data.go index fa41998..f0d4574 100644 --- a/ICCC/Message2Data.go +++ b/ICCC/Message2Data.go @@ -6,8 +6,13 @@ import ( "strconv" ) +// Function to convert an ICCC message to HTTP data. func message2Data(channel, command string, message interface{}) (data map[string][]string) { + + // Create the map: data = make(map[string][]string) + + // Add the meta information: data[`command`] = []string{command} data[`channel`] = []string{channel} @@ -15,9 +20,12 @@ func message2Data(channel, command string, message interface{}) (data map[string return } + // Use reflection to determine the types: element := reflect.ValueOf(message) elementType := element.Type() + // Iterate over all fields of the data type. + // Transform the data regarding the type. for i := 0; i < element.NumField(); i++ { field := element.Field(i) keyName := elementType.Field(i).Name @@ -44,6 +52,7 @@ func message2Data(channel, command string, message interface{}) (data map[string key := fmt.Sprintf(`ui8:%s`, keyName) data[key] = []string{strconv.FormatUint(field.Uint(), 16)} + // Case: Arrays... case `slice`: sliceLen := field.Len() if sliceLen > 0 { diff --git a/ICCC/Register2Database.go b/ICCC/Register2Database.go index 838e299..e19e3d6 100644 --- a/ICCC/Register2Database.go +++ b/ICCC/Register2Database.go @@ -7,10 +7,13 @@ import ( "gopkg.in/mgo.v2/bson" ) +// The internal function to register a command to ICCC. func register2Database(channel, command string) { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Register this ICCC command in to the database.`, `channel=`+channel, `command=`+command) + // // Case: Exist and active :) + // emptyEntry := Scheme.Listener{} selection := bson.D{{`Channel`, channel}, {`Command`, command}, {`IPAddressPort`, correctAddressWithPort}, {`IsActive`, true}} count1, _ := collectionListener.Find(selection).Count() @@ -20,7 +23,9 @@ func register2Database(channel, command string) { return } + // // Case: Exist but not active + // selection = bson.D{{`Channel`, channel}, {`Command`, command}, {`IPAddressPort`, correctAddressWithPort}, {`IsActive`, false}} notActiveEntry := Scheme.Listener{} collectionListener.Find(selection).One(¬ActiveEntry) @@ -32,7 +37,9 @@ func register2Database(channel, command string) { return } + // // Case: Not exist + // Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactNone, LM.MessageNameCONFIGURATION, `This ICCC command is not known.`, `Create now a new entry!`) entry := Scheme.Listener{} diff --git a/ICCC/RegisterHost2Database.go b/ICCC/RegisterHost2Database.go index 7073d4e..df8ba78 100644 --- a/ICCC/RegisterHost2Database.go +++ b/ICCC/RegisterHost2Database.go @@ -8,20 +8,31 @@ import ( "gopkg.in/mgo.v2/bson" ) +// Function to register this server to the ICCC. func registerHost2Database() { + + // Create the host entry: host := Scheme.Host{} host.Hostname = Tools.ThisHostname() host.IPAddressPort = correctAddressWithPort + // The query to find already existing entries: selection := bson.D{{`Hostname`, host.Hostname}, {`IPAddressPort`, host.IPAddressPort}} + + // Count the already existing entries: count, _ := collectionHosts.Find(selection).Count() + // Already exist? if count == 1 { + // Case: Exists! Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `This host is already registered!`, `host=`+host.Hostname, `address=`+host.IPAddressPort) } else { + // Case: Not exist. if errInsert := collectionHosts.Insert(host); errInsert != nil { + // Case: Was not able insert in the database Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to register this host.`, errInsert.Error(), `host=`+host.Hostname, `address=`+host.IPAddressPort) } else { + // Case: Everything was fine. Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `This host is now registered.`, `host=`+host.Hostname, `address=`+host.IPAddressPort) } } diff --git a/ICCC/Registrar.go b/ICCC/Registrar.go index 9dec531..db3b929 100644 --- a/ICCC/Registrar.go +++ b/ICCC/Registrar.go @@ -6,11 +6,15 @@ import ( LM "github.com/SommerEngineering/Ocean/Log/Meta" ) +// Register a command to ICCC for a specific channel. func Registrar(channel, command string, callback func(data map[string][]string)) { listenersLock.Lock() defer listenersLock.Unlock() + // Write the command to the database: register2Database(channel, command) + + // Register the command at the local cache: listeners[fmt.Sprintf(`%s::%s`, channel, command)] = callback Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `The registrar has registered a new ICCC command.`, `channel=`+channel, `command=`+command) diff --git a/ICCC/Scheme/Host.go b/ICCC/Scheme/Host.go index fa18f42..2d11418 100644 --- a/ICCC/Scheme/Host.go +++ b/ICCC/Scheme/Host.go @@ -1,5 +1,6 @@ package Scheme +// Scheme for the host database entry. type Host struct { Hostname string `bson:"Hostname"` IPAddressPort string `bson:"IPAddressPort"` diff --git a/ICCC/Scheme/Listener.go b/ICCC/Scheme/Listener.go index ac4f93c..794ce7b 100644 --- a/ICCC/Scheme/Listener.go +++ b/ICCC/Scheme/Listener.go @@ -1,5 +1,6 @@ package Scheme +// Type for the listener entries at the database. type Listener struct { Channel string `bson:"Channel"` Command string `bson:"Command"` diff --git a/ICCC/Send.go b/ICCC/Send.go index dfd94d2..98fa52d 100644 --- a/ICCC/Send.go +++ b/ICCC/Send.go @@ -9,11 +9,17 @@ import ( "net/url" ) +// Send a message to a listener. func sendMessage(listener Scheme.Listener, data map[string][]string) { - + // Convert the data and encode it: valuesHTTP := url.Values(data) + + // Add the communication password: valuesHTTP.Add(`InternalCommPassword`, Tools.InternalCommPassword()) + + // Try to deliver the message: if _, err := http.PostForm(`http://`+listener.IPAddressPort+`/ICCC`, valuesHTTP); err != nil { + // Case: Was not possible to deliver. Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameNETWORK, `Was not able to send the ICCC message.`, err.Error()) } diff --git a/ICCC/Shutdown.go b/ICCC/Shutdown.go index 6ba4694..20b04d3 100644 --- a/ICCC/Shutdown.go +++ b/ICCC/Shutdown.go @@ -7,27 +7,32 @@ import ( "gopkg.in/mgo.v2/bson" ) -/* -Please do not use this type. It is an internal type of Ocean to provide a shutdown function! -*/ +// Type to provide a shutdown function. type ShutdownFunction struct { } -/* -This function is called if the Ocean server is shutting down. -*/ +// The shutdown function for ICCC. func (a ShutdownFunction) Shutdown() { Log.LogShort(senderName, LM.CategoryAPP, LM.LevelWARN, LM.MessageNameSHUTDOWN, `Shutting down now all ICCC listener for this host.`) + // Define the database query: selection := bson.D{{`IPAddressPort`, correctAddressWithPort}} + + // Reserve the memory for an answer: entry := Scheme.Listener{} + + // Execute the query and iterate over the results: iterator := collectionListener.Find(selection).Iter() for iterator.Next(&entry) { + // Update the entry and set it to active=false: selectionUpdate := bson.D{{`Channel`, entry.Channel}, {`Command`, entry.Command}, {`IPAddressPort`, correctAddressWithPort}} entry.IsActive = false + + // Update the entry: collectionListener.Update(selectionUpdate, entry) } + // Disconnect the database: db.Logout() dbSession.Close() Log.LogShort(senderName, LM.CategoryAPP, LM.LevelWARN, LM.MessageNameSHUTDOWN, `Done shutting down now all ICCC listener for this host.`) diff --git a/ICCC/StartCacheTimer.go b/ICCC/StartCacheTimer.go new file mode 100644 index 0000000..68260d9 --- /dev/null +++ b/ICCC/StartCacheTimer.go @@ -0,0 +1,6 @@ +package ICCC + +// Starts the cache timer thread. +func StartCacheTimer() { + initCacheTimer() +} diff --git a/ICCC/SystemMessages/ICCCStartup.go b/ICCC/SystemMessages/ICCCStartup.go index 52a040f..5aa99b7 100644 --- a/ICCC/SystemMessages/ICCCStartup.go +++ b/ICCC/SystemMessages/ICCCStartup.go @@ -1,5 +1,6 @@ package SystemMessages +// Message type for the startup message: type ICCCStartUpMessage struct { PublicIPAddressAndPort string AdminIPAddressAndPort string diff --git a/ICCC/Variables.go b/ICCC/Variables.go index 8074694..c9a4693 100644 --- a/ICCC/Variables.go +++ b/ICCC/Variables.go @@ -7,26 +7,27 @@ import ( "sync" ) +// Some pre-defined channels: const ( - ChannelSYSTEM string = `System` - ChannelNUMGEN string = `System::NumGen` - ChannelSHUTDOWN string = `System::Shutdown` - ChannelSTARTUP string = `System::Startup` - ChannelICCC string = `System::ICCC` + ChannelSYSTEM string = `System` // The common system channel. + ChannelNUMGEN string = `System::NumGen` // A channel for the number generator. + ChannelSHUTDOWN string = `System::Shutdown` // A channel for system shutdown messages. + ChannelSTARTUP string = `System::Startup` // A channel for system startup messages. + ChannelICCC string = `System::ICCC` // A common ICCC channel. ) var ( - senderName LM.Sender = `ICCC` - db *mgo.Database = nil - dbSession *mgo.Session = nil - collectionListener *mgo.Collection = nil - collectionHosts *mgo.Collection = nil - reservedSystemChannels []string = []string{ChannelSYSTEM, ChannelNUMGEN, ChannelSHUTDOWN, ChannelSTARTUP, ChannelICCC} - listeners map[string]func(data map[string][]string) = nil - listenersLock sync.RWMutex = sync.RWMutex{} - cacheListenerDatabase *list.List = nil - cacheListenerDatabaseLock sync.RWMutex = sync.RWMutex{} - startCacheTimerLock sync.Mutex = sync.Mutex{} - cacheTimerRunning bool = false - correctAddressWithPort string = `` + senderName LM.Sender = `ICCC` // This is the name for logging event from this package + db *mgo.Database = nil // The database + dbSession *mgo.Session = nil // The database session + collectionListener *mgo.Collection = nil // The database collection for listeners + collectionHosts *mgo.Collection = nil // The database collection for hosts + reservedSystemChannels []string = []string{ChannelSYSTEM, ChannelNUMGEN, ChannelSHUTDOWN, ChannelSTARTUP, ChannelICCC} // The reserved and pre-defined system channels + listeners map[string]func(data map[string][]string) = nil // The listener cache for all local available listeners with local functions + listenersLock sync.RWMutex = sync.RWMutex{} // The mutex for the listener cache + cacheListenerDatabase *list.List = nil // The globally cache for all listeners from all servers + cacheListenerDatabaseLock sync.RWMutex = sync.RWMutex{} // The mutex for the globally cache + startCacheTimerLock sync.Mutex = sync.Mutex{} // Mutex for the start timer + cacheTimerRunning bool = false // Is the timer running? + correctAddressWithPort string = `` // The IP address and port of the this local server ) diff --git a/ICCC/WriteMessage2All.go b/ICCC/WriteMessage2All.go index e1d0869..063be44 100644 --- a/ICCC/WriteMessage2All.go +++ b/ICCC/WriteMessage2All.go @@ -6,20 +6,27 @@ import ( LM "github.com/SommerEngineering/Ocean/Log/Meta" ) +// Function to broadcast a message to all listeners. func WriteMessage2All(channel, command string, message interface{}) { cacheListenerDatabaseLock.RLock() defer cacheListenerDatabaseLock.RUnlock() + // Convert the message to HTTP data: data := message2Data(channel, command, message) counter := 0 + + // Loop over all listeners which are currently available at the cache: for entry := cacheListenerDatabase.Front(); entry != nil; entry = entry.Next() { listener := entry.Value.(Scheme.Listener) + + // If the channel and the command matches, deliver the message: if listener.Channel == channel && listener.Command == command { go sendMessage(listener, data) counter++ } } + // Was not able to deliver to any listener? if counter == 0 { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `It was not able to deliver this message, because no listener was found!`, `channel=`+channel, `command=`+command) } diff --git a/ICCC/WriteMessage2Any.go b/ICCC/WriteMessage2Any.go index 984c2aa..6d7c174 100644 --- a/ICCC/WriteMessage2Any.go +++ b/ICCC/WriteMessage2Any.go @@ -7,16 +7,22 @@ import ( "github.com/SommerEngineering/Ocean/Tools" ) +// Function to write a message to any listener. func WriteMessage2Any(channel, command string, message interface{}) { cacheListenerDatabaseLock.RLock() defer cacheListenerDatabaseLock.RUnlock() + // Convert the message to HTTP data: data := message2Data(channel, command, message) maxCount := cacheListenerDatabase.Len() entries := make([]Scheme.Listener, 0, maxCount) counter := 0 + + // Loop over all listeners which are currently present at the cache: for entry := cacheListenerDatabase.Front(); entry != nil; entry = entry.Next() { listener := entry.Value.(Scheme.Listener) + + // If the channel and the command matches, store the listener: if listener.Channel == channel && listener.Command == command { entries = entries[:len(entries)+1] entries[counter] = listener @@ -25,9 +31,11 @@ func WriteMessage2Any(channel, command string, message interface{}) { count := len(entries) if count > 0 { + // Case: Find at least one possible listener. Choose a random one and deliver: listener := entries[Tools.RandomInteger(count)] go sendMessage(listener, data) } else { + // Case: Find no listener at all. Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `It was not able to deliver this message to any listener, because no listener was found!`, `channel=`+channel, `command=`+command) } } diff --git a/Log/Device/Device.go b/Log/Device/Device.go index e77bc1a..e826601 100644 --- a/Log/Device/Device.go +++ b/Log/Device/Device.go @@ -4,6 +4,7 @@ import ( "github.com/SommerEngineering/Ocean/Log/Meta" ) +// The interface for every logging device. type Device interface { Log(logEntries []Meta.Entry) Flush() diff --git a/Log/DeviceConsole/ActivateLoggingDevice.go b/Log/DeviceConsole/ActivateLoggingDevice.go index 903d053..c1cc818 100644 --- a/Log/DeviceConsole/ActivateLoggingDevice.go +++ b/Log/DeviceConsole/ActivateLoggingDevice.go @@ -4,6 +4,7 @@ import ( "github.com/SommerEngineering/Ocean/Log" ) +// Function with the setup of the logging device. func ActivateLoggingDevice() { Log.AddLoggingDevice(Console{}) } diff --git a/Log/DeviceConsole/Console.go b/Log/DeviceConsole/Console.go index e6e545a..667ae17 100644 --- a/Log/DeviceConsole/Console.go +++ b/Log/DeviceConsole/Console.go @@ -5,9 +5,11 @@ import ( "github.com/SommerEngineering/Ocean/Log/Meta" ) +// The logging device. type Console struct { } +// This function is the interface between the logging system and the console logger. func (dev Console) Log(entries []Meta.Entry) { for _, entry := range entries { fmt.Println(entry.Format()) diff --git a/Log/DeviceDatabase/ActivateLoggingDevice.go b/Log/DeviceDatabase/ActivateLoggingDevice.go index be70395..b6a8e4d 100644 --- a/Log/DeviceDatabase/ActivateLoggingDevice.go +++ b/Log/DeviceDatabase/ActivateLoggingDevice.go @@ -4,6 +4,7 @@ import ( "github.com/SommerEngineering/Ocean/Log" ) +// Function with the setup of the logging device. func ActivateLoggingDevice() { Log.AddLoggingDevice(Database{}) } diff --git a/Log/DeviceDatabase/CacheFull.go b/Log/DeviceDatabase/CacheFull.go new file mode 100644 index 0000000..3ff5bf4 --- /dev/null +++ b/Log/DeviceDatabase/CacheFull.go @@ -0,0 +1,18 @@ +package DeviceDatabase + +// Function to check if the cache is full. If so, write all events to the database. +func cacheFull() { + mutexCacheFull.Lock() + defer mutexCacheFull.Unlock() + + // Is the cache full? + if len(cache) < cacheSizeNumberOfEvents { + // Case: Cache is not full. + return + } + + // Case: The cache is full. Write all events to the database. + for counter := 0; counter < cacheSizeNumberOfEvents; counter++ { + write2Database(<-cache) + } +} diff --git a/Log/DeviceDatabase/CacheRefreshMessageNames.go b/Log/DeviceDatabase/CacheRefreshMessageNames.go new file mode 100644 index 0000000..44553de --- /dev/null +++ b/Log/DeviceDatabase/CacheRefreshMessageNames.go @@ -0,0 +1,37 @@ +package DeviceDatabase + +import ( + "github.com/SommerEngineering/Ocean/Log" + LM "github.com/SommerEngineering/Ocean/Log/Meta" + "github.com/SommerEngineering/Ocean/Shutdown" + "time" +) + +// Function for the thread which maintain the message name cache. +func cacheRefreshMessageNames() { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `The message names' refresh thread is now running.`) + + // Create an own thread: + go func() { + // Endless loop: + for true { + // Read the message names rom the DB: + data := readMessageNamesFromDB() + mutexCacheMessageNames.Lock() + + // Overwrite the cache: + cacheMessageNames = data + mutexCacheMessageNames.Unlock() + + // Sleep some time: + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelTALKATIVE, LM.MessageNameEXECUTE, `The message names' cache was refreshed.`) + time.Sleep(time.Duration(nameCachesRefreshTimeSeconds) * time.Second) + + // Case: The server goes down now. + if Shutdown.IsDown() { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityLow, LM.ImpactLow, LM.MessageNameSHUTDOWN, `The message name's refresh thread is now shutting down.`) + return + } + } + }() +} diff --git a/Log/DeviceDatabase/CacheRefreshSenderNames.go b/Log/DeviceDatabase/CacheRefreshSenderNames.go new file mode 100644 index 0000000..86486fb --- /dev/null +++ b/Log/DeviceDatabase/CacheRefreshSenderNames.go @@ -0,0 +1,38 @@ +package DeviceDatabase + +import ( + "github.com/SommerEngineering/Ocean/Log" + LM "github.com/SommerEngineering/Ocean/Log/Meta" + "github.com/SommerEngineering/Ocean/Shutdown" + "time" +) + +// Function for the thread which maintain the sender name cache. +func cacheRefreshSenderNames() { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `The sender names' refresh thread is now running.`) + + // Use an extra thread: + go func() { + // Endless lopp: + for true { + + // Read the sender names from the DB: + data := readSenderNamesFromDB() + mutexCacheSenderNames.Lock() + + // Overwrite the cache: + cacheSenderNames = data + mutexCacheSenderNames.Unlock() + + // Sleep some time: + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelTALKATIVE, LM.MessageNameEXECUTE, `The sender names' cache was refreshed.`) + time.Sleep(time.Duration(nameCachesRefreshTimeSeconds) * time.Second) + + // Case: The server is going down now. + if Shutdown.IsDown() { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityLow, LM.ImpactLow, LM.MessageNameSHUTDOWN, `The sender name's refresh thread is now shutting down.`) + return + } + } + }() +} diff --git a/Log/DeviceDatabase/Flush.go b/Log/DeviceDatabase/Flush.go index 9fdc398..133c82b 100644 --- a/Log/DeviceDatabase/Flush.go +++ b/Log/DeviceDatabase/Flush.go @@ -1,5 +1,6 @@ package DeviceDatabase +// Flush the cache and write all messages to the database. func (dev Database) Flush() { mutexCacheFull.Lock() defer mutexCacheFull.Unlock() @@ -9,6 +10,7 @@ func (dev Database) Flush() { write2Database(<-cache) } + // Shutdown the database connection: logDB.Logout() logDBSession.Close() } diff --git a/Log/DeviceDatabase/Format.go b/Log/DeviceDatabase/Format.go index 979e812..467485a 100644 --- a/Log/DeviceDatabase/Format.go +++ b/Log/DeviceDatabase/Format.go @@ -4,8 +4,9 @@ import ( "github.com/SommerEngineering/Ocean/Log/Meta" ) +// Function to format a logging database entry as string. func (entry LogDBEntry) Format() (result string) { - + // First, we convert the logging db entry to the common logging type: converted := Meta.Entry{} converted.Time = entry.TimeUTC converted.Project = entry.Project @@ -18,6 +19,7 @@ func (entry LogDBEntry) Format() (result string) { converted.MessageDescription = entry.MessageDescription converted.Parameters = entry.Parameters + // Second, we can use then the format operation of these type: result = converted.Format() return } diff --git a/Log/DeviceDatabase/Init.go b/Log/DeviceDatabase/Init.go index c355e39..3c93d56 100644 --- a/Log/DeviceDatabase/Init.go +++ b/Log/DeviceDatabase/Init.go @@ -8,12 +8,19 @@ import ( "strconv" ) +// The init function for this package. func init() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Starting now the database logging.`) defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Starting the database logging done.`) + // Init the database first: initDatabase() + + // + // Read all configuration values: + // + if value, err := strconv.Atoi(ConfigurationDB.Read(`LogDBCacheSizeNumberOfEvents`)); err != nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityHigh, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `It was not possible to read the LogDBCacheSizeNumberOfEvents configuration.`, `The default value will be used.`, fmt.Sprintf(`Default value is %d.`, cacheSizeNumberOfEvents)) } else { @@ -35,8 +42,15 @@ func init() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `Configuration LogDBWebInterfaceNameCacheRefreshTimeSeconds was loaded.`, fmt.Sprintf(`The value is %d.`, nameCachesRefreshTimeSeconds)) } + // Create the cache: cache = make(chan LogDBEntry, cacheSizeNumberOfEvents) - initTimeout() + + // Starts a thread to write events based on time-outs: + startTimeout() + + // Starts a thread to refresh the sender name cache: cacheRefreshSenderNames() + + // Starts a thread to refresh the message name cache: cacheRefreshMessageNames() } diff --git a/Log/DeviceDatabase/InitDB.go b/Log/DeviceDatabase/InitDB.go index e2a66fa..9c47e6a 100644 --- a/Log/DeviceDatabase/InitDB.go +++ b/Log/DeviceDatabase/InitDB.go @@ -12,19 +12,26 @@ import ( "time" ) +// Init the database for the logging. func initDatabase() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Checking and init the logging database collection.`) defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Checking and init the logging database collection done.`) + // Read the configuration values for the logging database: databaseHost := ConfigurationDB.Read(`LogDBHost`) databaseDB := ConfigurationDB.Read(`LogDBDatabase`) databaseUsername := ConfigurationDB.Read(`LogDBUsername`) databasePassword := ConfigurationDB.Read(`LogDBPassword`) + + // Should the logging events at the database expire? expire := strings.ToLower(ConfigurationDB.Read(`LogDBEventsExpire`)) == `true` + + // The default values for the TTL (time to live): expireAfterDays := 21900 // 60 years ~ maximum of MongoDB expireValue4DisabledFunction := 21900 // 60 years ~ maximum of MongoDB + // Try to read the configured value for the TTL: if value, errValue := strconv.Atoi(ConfigurationDB.Read(`LogDBEventsExpireAfterDays`)); errValue != nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityMiddle, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `It was not possible to read the configuration for the expire time of logging events. Log events will not expire any more.`, errValue.Error()) expire = false @@ -86,6 +93,9 @@ func initDatabase() { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, fmt.Sprintf(`Update the expire policy for the logging database done.`)) } + // + // Ensure that all necessary indexes are existing: + // indexProject := mgo.Index{} indexProject.Key = []string{`Project`} logDBCollection.EnsureIndex(indexProject) diff --git a/Log/DeviceDatabase/ReadCustom.go b/Log/DeviceDatabase/ReadCustom.go index 04a7045..89927ba 100644 --- a/Log/DeviceDatabase/ReadCustom.go +++ b/Log/DeviceDatabase/ReadCustom.go @@ -7,21 +7,27 @@ import ( func ReadCustom(timeRange, logLevel, logCategory, logImpact, logSeverity, logMessageName, logSender, logPage string) (events []LogDBEntry) { // - // TODO => Is currently stub + // TODO => Is currently a stub // + // Define the query: query := logDBCollection.Find(bson.D{}).Sort(`-TimeUTC`).Limit(26) count := 26 + // Execute the query and count the results: if n, err := query.Count(); err == nil { count = n } + // The iterator for the results: iter := query.Iter() entry := LogDBEntry{} pos := 0 + + // Reserve the memory for the results: events = make([]LogDBEntry, count) + // Loop over all entries and store it: for iter.Next(&entry) { events[pos] = entry pos++ diff --git a/Log/DeviceDatabase/ReadLatest.go b/Log/DeviceDatabase/ReadLatest.go index 9a8054e..a544f78 100644 --- a/Log/DeviceDatabase/ReadLatest.go +++ b/Log/DeviceDatabase/ReadLatest.go @@ -4,20 +4,26 @@ import ( "gopkg.in/mgo.v2/bson" ) +// Read the latest logging events from the database. func ReadLatest() (events []LogDBEntry) { - + // Define the query: query := logDBCollection.Find(bson.D{}).Sort(`-TimeUTC`).Limit(26) count := 26 + // Execute the query and count the results: if n, err := query.Count(); err == nil { count = n } + // The iterator for the results: iter := query.Iter() entry := LogDBEntry{} pos := 0 + + // Reserve the memory for the results: events = make([]LogDBEntry, count) + // Loop over all entries and store it: for iter.Next(&entry) { events[pos] = entry pos++ diff --git a/Log/DeviceDatabase/ReadMessageNames.go b/Log/DeviceDatabase/ReadMessageNames.go index ecd93af..8bf77c5 100644 --- a/Log/DeviceDatabase/ReadMessageNames.go +++ b/Log/DeviceDatabase/ReadMessageNames.go @@ -1,51 +1,9 @@ package DeviceDatabase -import ( - "github.com/SommerEngineering/Ocean/Log" - LM "github.com/SommerEngineering/Ocean/Log/Meta" - "github.com/SommerEngineering/Ocean/Shutdown" - "gopkg.in/mgo.v2/bson" - "sort" - "time" -) - +// Read the message names out of the cache. func ReadMessageNames() (messageNames []string) { mutexCacheMessageNames.RLock() defer mutexCacheMessageNames.RUnlock() messageNames = cacheMessageNames return } - -func cacheRefreshMessageNames() { - Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `The message names' refresh thread is now running.`) - go func() { - for true { - - data := readMessageNamesFromDB() - mutexCacheMessageNames.Lock() - cacheMessageNames = data - mutexCacheMessageNames.Unlock() - - Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelTALKATIVE, LM.MessageNameEXECUTE, `The message names' cache was refreshed.`) - time.Sleep(time.Duration(nameCachesRefreshTimeSeconds) * time.Second) - - if Shutdown.IsDown() { - Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityLow, LM.ImpactLow, LM.MessageNameSHUTDOWN, `The message name's refresh thread is now shutting down.`) - return - } - } - }() -} - -func readMessageNamesFromDB() (result []string) { - - var nextMessageNames []string - if err := logDBCollection.Find(bson.D{}).Distinct(`MessageName`, &nextMessageNames); err != nil { - Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.MessageNameDATABASE, `Was not able to read the message names from the database.`, err.Error()) - return - } - - sort.Strings(nextMessageNames) - result = nextMessageNames - return -} diff --git a/Log/DeviceDatabase/ReadMessageNamesFromDB.go b/Log/DeviceDatabase/ReadMessageNamesFromDB.go new file mode 100644 index 0000000..e0703a8 --- /dev/null +++ b/Log/DeviceDatabase/ReadMessageNamesFromDB.go @@ -0,0 +1,23 @@ +package DeviceDatabase + +import ( + "github.com/SommerEngineering/Ocean/Log" + LM "github.com/SommerEngineering/Ocean/Log/Meta" + "gopkg.in/mgo.v2/bson" + "sort" +) + +// Read the message names from the database without any cache. +func readMessageNamesFromDB() (result []string) { + var nextMessageNames []string + if err := logDBCollection.Find(bson.D{}).Distinct(`MessageName`, &nextMessageNames); err != nil { + // Case: Error, was not able to write the event to the database: + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.MessageNameDATABASE, `Was not able to read the message names from the database.`, err.Error()) + return + } + + // Sort the sender names: + sort.Strings(nextMessageNames) + result = nextMessageNames + return +} diff --git a/Log/DeviceDatabase/ReadSenderNames.go b/Log/DeviceDatabase/ReadSenderNames.go index a1e411e..296b564 100644 --- a/Log/DeviceDatabase/ReadSenderNames.go +++ b/Log/DeviceDatabase/ReadSenderNames.go @@ -1,51 +1,9 @@ package DeviceDatabase -import ( - "github.com/SommerEngineering/Ocean/Log" - LM "github.com/SommerEngineering/Ocean/Log/Meta" - "github.com/SommerEngineering/Ocean/Shutdown" - "gopkg.in/mgo.v2/bson" - "sort" - "time" -) - +// Read the sender names out of the cache. func ReadSenderNames() (senderNames []string) { mutexCacheSenderNames.RLock() defer mutexCacheSenderNames.RUnlock() senderNames = cacheSenderNames return } - -func cacheRefreshSenderNames() { - Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `The sender names' refresh thread is now running.`) - go func() { - for true { - - data := readSenderNamesFromDB() - mutexCacheSenderNames.Lock() - cacheSenderNames = data - mutexCacheSenderNames.Unlock() - - Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelTALKATIVE, LM.MessageNameEXECUTE, `The sender names' cache was refreshed.`) - time.Sleep(time.Duration(nameCachesRefreshTimeSeconds) * time.Second) - - if Shutdown.IsDown() { - Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityLow, LM.ImpactLow, LM.MessageNameSHUTDOWN, `The sender name's refresh thread is now shutting down.`) - return - } - } - }() -} - -func readSenderNamesFromDB() (result []string) { - - var nextSenderNames []string - if err := logDBCollection.Find(bson.D{}).Distinct(`Sender`, &nextSenderNames); err != nil { - Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.MessageNameDATABASE, `Was not able to read the sender names from the database.`, err.Error()) - return - } - - sort.Strings(nextSenderNames) - result = nextSenderNames - return -} diff --git a/Log/DeviceDatabase/ReadSenderNamesFromDB.go b/Log/DeviceDatabase/ReadSenderNamesFromDB.go new file mode 100644 index 0000000..321d2e4 --- /dev/null +++ b/Log/DeviceDatabase/ReadSenderNamesFromDB.go @@ -0,0 +1,23 @@ +package DeviceDatabase + +import ( + "github.com/SommerEngineering/Ocean/Log" + LM "github.com/SommerEngineering/Ocean/Log/Meta" + "gopkg.in/mgo.v2/bson" + "sort" +) + +// Reads the sender names from the database without any caching. +func readSenderNamesFromDB() (result []string) { + var nextSenderNames []string + if err := logDBCollection.Find(bson.D{}).Distinct(`Sender`, &nextSenderNames); err != nil { + // Case: Was not possible to write to the database. + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.MessageNameDATABASE, `Was not able to read the sender names from the database.`, err.Error()) + return + } + + // Sort the sender names: + sort.Strings(nextSenderNames) + result = nextSenderNames + return +} diff --git a/Log/DeviceDatabase/ReceiveLogging.go b/Log/DeviceDatabase/ReceiveLogging.go index c4e74f7..8bfbe9b 100644 --- a/Log/DeviceDatabase/ReceiveLogging.go +++ b/Log/DeviceDatabase/ReceiveLogging.go @@ -4,14 +4,13 @@ import ( "github.com/SommerEngineering/Ocean/Log/Meta" ) -type Database struct { -} - +// This function is the interface between the logging system and the database logger. func (dev Database) Log(entries []Meta.Entry) { // // Cannot log here to prevent endless loop (consumer is also producer) // + // Write every incoming batch to the cache: write2Cache(entries) } diff --git a/Log/DeviceDatabase/Scheme.go b/Log/DeviceDatabase/Scheme.go index 60853de..6a4e1e9 100644 --- a/Log/DeviceDatabase/Scheme.go +++ b/Log/DeviceDatabase/Scheme.go @@ -4,6 +4,7 @@ import ( "time" ) +// The type for the database logging. type LogDBEntry struct { TimeUTC time.Time `bson:"TimeUTC"` Project string `bson:"Project"` @@ -17,8 +18,13 @@ type LogDBEntry struct { Parameters []string `bson:"Parameters"` } +// A type for the TTL (time to live) for the index. type TTLUpdateResult struct { ExpireAfterSeconds_old int32 `bson:"expireAfterSeconds_old"` ExpireAfterSeconds_new int32 `bson:"expireAfterSeconds_new"` Ok int32 `bson:"ok"` } + +// The logging device. +type Database struct { +} diff --git a/Log/DeviceDatabase/ReadFromCache.go b/Log/DeviceDatabase/StartTimeout.go similarity index 54% rename from Log/DeviceDatabase/ReadFromCache.go rename to Log/DeviceDatabase/StartTimeout.go index d5c556f..197786f 100644 --- a/Log/DeviceDatabase/ReadFromCache.go +++ b/Log/DeviceDatabase/StartTimeout.go @@ -1,41 +1,32 @@ -package DeviceDatabase - -import ( - "github.com/SommerEngineering/Ocean/Shutdown" - "time" -) - -// Case: The cache is full -func cacheFull() { - mutexCacheFull.Lock() - defer mutexCacheFull.Unlock() - - if len(cache) < cacheSizeNumberOfEvents { - return - } - - for counter := 0; counter < cacheSizeNumberOfEvents; counter++ { - write2Database(<-cache) - } -} - -// Case: Time out -func initTimeout() { - - go func() { - for { - - if Shutdown.IsDown() { - return - } - - time.Sleep(time.Duration(cacheSizeTime2FlushSeconds) * time.Second) - mutexCacheFull.Lock() - amount := len(cache) - for counter := 0; counter < amount; counter++ { - write2Database(<-cache) - } - mutexCacheFull.Unlock() - } - }() -} +package DeviceDatabase + +import ( + "github.com/SommerEngineering/Ocean/Shutdown" + "time" +) + +// The timeout function writes the logging events to the database afer some time. +func startTimeout() { + + // Starts a new thread: + go func() { + for { + + // Case: The system goes down now. + if Shutdown.IsDown() { + return + } + + // Wait for the right time: + time.Sleep(time.Duration(cacheSizeTime2FlushSeconds) * time.Second) + + // Write the events to the database: + mutexCacheFull.Lock() + amount := len(cache) + for counter := 0; counter < amount; counter++ { + write2Database(<-cache) + } + mutexCacheFull.Unlock() + } + }() +} diff --git a/Log/DeviceDatabase/Variables.go b/Log/DeviceDatabase/Variables.go index 8579c4b..62d34fe 100644 --- a/Log/DeviceDatabase/Variables.go +++ b/Log/DeviceDatabase/Variables.go @@ -7,6 +7,7 @@ import ( ) var ( + // This is the name for logging event from this package: senderName LM.Sender = `System::Logger::Database` mutexCacheFull sync.Mutex = sync.Mutex{} mutexCacheSenderNames sync.RWMutex = sync.RWMutex{} diff --git a/Log/DeviceDatabase/Write2Cache.go b/Log/DeviceDatabase/Write2Cache.go index de02a24..307eefc 100644 --- a/Log/DeviceDatabase/Write2Cache.go +++ b/Log/DeviceDatabase/Write2Cache.go @@ -4,12 +4,17 @@ import ( "github.com/SommerEngineering/Ocean/Log/Meta" ) +// This function writes a batch of log entries to the cache. func write2Cache(entries []Meta.Entry) { + // Loop over each entry: for _, entry := range entries { + // If the cache is full, execute it: if len(cache) == cacheSizeNumberOfEvents { + // Execute the cache with a new thread: go cacheFull() } + // Convert the log entry to the database format: logDBentry := LogDBEntry{} logDBentry.Category = entry.Category.Format() logDBentry.Impact = entry.Impact.Format() @@ -21,6 +26,8 @@ func write2Cache(entries []Meta.Entry) { logDBentry.Sender = string(entry.Sender) logDBentry.Severity = entry.Severity.Format() logDBentry.TimeUTC = entry.Time + + // Write it to the cache: cache <- logDBentry } } diff --git a/Log/DeviceDatabase/Write2Database.go b/Log/DeviceDatabase/Write2Database.go index 12e2481..d468236 100644 --- a/Log/DeviceDatabase/Write2Database.go +++ b/Log/DeviceDatabase/Write2Database.go @@ -1,7 +1,15 @@ package DeviceDatabase +import ( + "fmt" +) + +// Function to write a logging event to the database. func write2Database(entry LogDBEntry) { + // Try to write the event to the database: if err := logDBCollection.Insert(entry); err != nil { - // Can not log here to prevent endless loop (consumer is also producer) + // Case: Error! + // Cannot log here to prevent endless loop (consumer is also producer) + fmt.Printf("Was not able to write a logging event to the database: '%s'. The log entry was: '%s'.\n", err.Error(), entry.Format()) } } diff --git a/Log/DeviceDelay.go b/Log/DeviceDelay.go index ff2c189..e13d518 100644 --- a/Log/DeviceDelay.go +++ b/Log/DeviceDelay.go @@ -5,18 +5,18 @@ import ( "github.com/SommerEngineering/Ocean/Log/Meta" ) +// Queue a log event before delivery to the devices. func deviceDelay(newEntry Meta.Entry) { defer checkDeviceDelaySize() - // Insert the new entry at the correct position (time)! + // Insert the new entry at the correct position (time). + // To ensure that the causality is guaranteed. for logEvent := deviceDelayBuffer.Front(); logEvent != nil; logEvent = logEvent.Next() { currentEvent := logEvent.Value.(Meta.Entry) if newEntry.Time.Before(currentEvent.Time) { - mutexDeviceDelays.Lock() deviceDelayBuffer.InsertBefore(newEntry, logEvent) mutexDeviceDelays.Unlock() - return } } @@ -27,19 +27,32 @@ func deviceDelay(newEntry Meta.Entry) { mutexDeviceDelays.Unlock() } +// Check if the size of the buffer is huge enough to deliver entries. func checkDeviceDelaySize() { + + // Get exklusive access: mutexDeviceDelays.Lock() + + // Is the size huge enough? if deviceDelayBuffer.Len() >= logDeviceDelayNumberEvents { + + // Read all entries: dataArray := logEntryListToArray(deviceDelayBuffer) + + // Re-init the buffer: deviceDelayBuffer.Init() + // Loop over all devices: mutexDevices.RLock() for entry := devices.Front(); entry != nil; entry = entry.Next() { dev := entry.Value.(Device.Device) + + // Deliver the data with a new thread: go dev.Log(dataArray) } mutexDevices.RUnlock() } + // Release the lock: mutexDeviceDelays.Unlock() } diff --git a/Log/Flush.go b/Log/Flush.go index f4f1aaa..cf24d96 100644 --- a/Log/Flush.go +++ b/Log/Flush.go @@ -4,22 +4,25 @@ import ( "github.com/SommerEngineering/Ocean/Log/Device" ) -/* -Please do not call this function your self! This function allows Ocean to flush the logging at the shutting down case. -*/ +// Function to force all buffers to flush the events. func Flush() { + + // Close the entry buffer: mutexChannel.Lock() channelReady = false close(entriesBuffer) mutexChannel.Unlock() + // Wait that the scheduler is done: <-schedulerExitSignal + // Get all log entries from the device delay buffer: mutexDeviceDelays.Lock() dataArray := logEntryListToArray(deviceDelayBuffer) deviceDelayBuffer.Init() mutexDeviceDelays.Unlock() + // Deliver all the events to all devices: mutexDevices.RLock() for entry := devices.Front(); entry != nil; entry = entry.Next() { dev := entry.Value.(Device.Device) diff --git a/Log/Handlers.go b/Log/Handlers.go index d96437b..dee7bc0 100644 --- a/Log/Handlers.go +++ b/Log/Handlers.go @@ -7,12 +7,12 @@ import ( "time" ) +// Writes a log message to the channel. func writeToChannel(logEntry Meta.Entry) { select { case entriesBuffer <- logEntry: case <-time.After(time.Duration(int64(logBufferTimeoutSeconds)) * time.Second): - - // Warn: Can not log here to prevent endless loop and memory leak! + // Warn: Cannot log here to prevent endless loop and memory leak! fmt.Println(`Warning: Was not able to write to the logging buffer! Message=` + logEntry.Format()) } } @@ -110,6 +110,7 @@ func LogShort(sender Meta.Sender, category Meta.Category, level Meta.Level, mess TakeEntry(entry) } +// Removes white spaces from the message. func clearEntry(entry Meta.Entry) (result Meta.Entry) { entry.MessageDescription = removeWhitespaces(entry.MessageDescription) entry.Parameters = clearParameters(entry.Parameters) @@ -117,6 +118,7 @@ func clearEntry(entry Meta.Entry) (result Meta.Entry) { return } +// Remove white spaces from the parameters. func clearParameters(oldParameters []string) (result []string) { for n := 0; n < len(oldParameters); n++ { oldParameters[n] = removeWhitespaces(oldParameters[n]) @@ -126,6 +128,7 @@ func clearParameters(oldParameters []string) (result []string) { return } +// Removes white spaces from a string. func removeWhitespaces(text string) (result string) { text = strings.Replace(text, "\n", ` `, -1) text = strings.Replace(text, "\t", ` `, -1) diff --git a/Log/Init.go b/Log/Init.go index 34a9287..1c9ab3c 100644 --- a/Log/Init.go +++ b/Log/Init.go @@ -3,44 +3,29 @@ package Log import ( "container/list" "github.com/SommerEngineering/Ocean/Log/Meta" - "io/ioutil" - "os" - "path/filepath" "strconv" - "strings" "sync" ) -func readProjectName() { - if currentDir, dirError := os.Getwd(); dirError != nil { - panic(`Can not read the current working directory and therefore can not read the project name!`) - } else { - filename := filepath.Join(currentDir, `project.name`) - if _, errFile := os.Stat(filename); errFile != nil { - if os.IsNotExist(errFile) { - panic(`Can not read the project name file 'project.name': File not found!`) - } else { - panic(`Can not read the project name file 'project.name': ` + errFile.Error()) - } - } - - if projectNameBytes, errRead := ioutil.ReadFile(filename); errRead != nil { - panic(`Can not read the project name file 'project.name': ` + errRead.Error()) - } else { - projectName = string(projectNameBytes) - projectName = strings.TrimSpace(projectName) - } - } -} - +// Init the logging package. func init() { + + // Read the project name: readProjectName() + + // Create the mutexe: mutexDeviceDelays = sync.Mutex{} mutexPreChannelBuffer = sync.Mutex{} mutexChannel = sync.RWMutex{} + + // Create buffers: preChannelBuffer = list.New() deviceDelayBuffer = list.New() + + // Create the device list: devices = list.New() + + // Channel to exit the scheduler: schedulerExitSignal = make(chan bool) initTimer() @@ -48,8 +33,10 @@ func init() { } func initCode() { + // Creates the buffer for logging entries: entriesBuffer = make(chan Meta.Entry, logBufferSize) - LogShort(senderName, Meta.CategorySYSTEM, Meta.LevelINFO, `Starting`, `The logger is now starting.`, `logBufferSize=`+strconv.Itoa(int(logBufferSize)), `logBufferTimeoutSeconds=`+strconv.Itoa(int(logBufferTimeoutSeconds))) + + // Start the scheduler as new thread: go scheduler(entriesBuffer) } diff --git a/Log/Array.go b/Log/LogEntryListToArray.go similarity index 87% rename from Log/Array.go rename to Log/LogEntryListToArray.go index 98af805..385fd06 100644 --- a/Log/Array.go +++ b/Log/LogEntryListToArray.go @@ -1,18 +1,19 @@ -package Log - -import ( - "container/list" - "github.com/SommerEngineering/Ocean/Log/Meta" -) - -func logEntryListToArray(data *list.List) (result []Meta.Entry) { - count := data.Len() - result = make([]Meta.Entry, count, count) - position := 0 - for entry := data.Front(); entry != nil; entry = entry.Next() { - result[position] = entry.Value.(Meta.Entry) - position++ - } - - return -} +package Log + +import ( + "container/list" + "github.com/SommerEngineering/Ocean/Log/Meta" +) + +// Converts a list with logging events to an array. +func logEntryListToArray(data *list.List) (result []Meta.Entry) { + count := data.Len() + result = make([]Meta.Entry, count, count) + position := 0 + for entry := data.Front(); entry != nil; entry = entry.Next() { + result[position] = entry.Value.(Meta.Entry) + position++ + } + + return +} diff --git a/Log/LoggingIsReady.go b/Log/LoggingIsReady.go index df3490a..a1e2820 100644 --- a/Log/LoggingIsReady.go +++ b/Log/LoggingIsReady.go @@ -1,7 +1,8 @@ package Log /* -This function is used just internal by Ocean. Please do not call this function by your self! +A function to change the state of the logging after the database is +accessible. */ func LoggingIsReady() { channelReady = true diff --git a/Log/Meta/Category.go b/Log/Meta/Category.go index fb1b770..05ef757 100644 --- a/Log/Meta/Category.go +++ b/Log/Meta/Category.go @@ -3,12 +3,13 @@ package Meta type Category byte const ( - CategoryBUSINESS = Category(iota) - CategorySYSTEM = Category(iota) - CategoryAPP = Category(iota) - CategoryUSER = Category(iota) + CategoryBUSINESS = Category(iota) // Business category + CategorySYSTEM = Category(iota) // System category + CategoryAPP = Category(iota) // Application category + CategoryUSER = Category(iota) // User category ) +// Formats a category as string. func (cat Category) Format() (result string) { switch cat { case CategoryBUSINESS: @@ -26,6 +27,7 @@ func (cat Category) Format() (result string) { return } +// Parse a category from a string. func ParseCategory(cat string) (value Category) { switch cat { case `C:BUSINESS`: diff --git a/Log/Meta/Entry.go b/Log/Meta/Entry.go index 43ae1ca..849f88d 100644 --- a/Log/Meta/Entry.go +++ b/Log/Meta/Entry.go @@ -4,6 +4,7 @@ import ( "time" ) +// Type for a log entry. type Entry struct { Project string Time time.Time diff --git a/Log/Meta/Format.go b/Log/Meta/Format.go index acee6ab..f89e5e9 100644 --- a/Log/Meta/Format.go +++ b/Log/Meta/Format.go @@ -7,18 +7,22 @@ import ( "time" ) +// Formats a log entry as string. func (entry Entry) Format() (result string) { + // Force the maximum length for a sender: lenSender := len(entry.Sender) if lenSender > 40 { lenSender = 40 } + // Force the maximum length for the message name: lenMessageName := len(entry.MessageName) if lenMessageName > 26 { lenMessageName = 26 } + // Force the maximum length for the project name: lenProject := len(entry.Project) if lenProject > 10 { lenProject = 10 @@ -29,21 +33,23 @@ func (entry Entry) Format() (result string) { messageDescription = strings.Replace(messageDescription, "\t", ` `, -1) messageDescription = strings.Replace(messageDescription, "\r", ` `, -1) + // Format the basic fields of the log message: result = fmt.Sprintf(` [â– ] P:%10s [â– ] %s [â– ] %10s [â– ] %11s [â– ] %10s [â– ] %10s [â– ] sender: %-40s [â– ] name: %-26s [â– ] %s [â– ]`, entry.Project[:lenProject], formatTime(entry.Time), entry.Category.Format(), entry.Level.Format(), entry.Severity.Format(), entry.Impact.Format(), entry.Sender[:lenSender], entry.MessageName[:lenMessageName], messageDescription) + // Formats the parameters: for _, param := range entry.Parameters { - paramText := param paramText = strings.Replace(paramText, "\n", ` `, -1) paramText = strings.Replace(paramText, "\t", ` `, -1) paramText = strings.Replace(paramText, "\r", ` `, -1) - result = fmt.Sprintf(`%s %s [â– ]`, result, paramText) } return } +// Formats the given time as YYYYMMdd HHmmss.fff! This function is necessary +// to prevent a import cycle. func formatTime(t1 time.Time) (result string) { var year int = t1.Year() var month int = int(t1.Month()) diff --git a/Log/Meta/Impact.go b/Log/Meta/Impact.go index 8d382c2..89f41f5 100644 --- a/Log/Meta/Impact.go +++ b/Log/Meta/Impact.go @@ -3,14 +3,15 @@ package Meta type Impact byte const ( - ImpactNone = Impact(iota) - ImpactLow = Impact(iota) - ImpactMiddle = Impact(iota) - ImpactHigh = Impact(iota) - ImpactCritical = Impact(iota) - ImpactUnknown = Impact(iota) + ImpactNone = Impact(iota) // None impact + ImpactLow = Impact(iota) // Low impact + ImpactMiddle = Impact(iota) // Middle impact + ImpactHigh = Impact(iota) // High impact + ImpactCritical = Impact(iota) // Critical impact + ImpactUnknown = Impact(iota) // Unknown impact ) +// Formats a impact as string. func (pri Impact) Format() (result string) { switch pri { case ImpactCritical: @@ -32,6 +33,7 @@ func (pri Impact) Format() (result string) { return } +// Parse a impact from a string. func ParseImpact(pri string) (value Impact) { switch pri { case `I:CRITICAL`: diff --git a/Log/Meta/Level.go b/Log/Meta/Level.go index 774d532..eda6706 100644 --- a/Log/Meta/Level.go +++ b/Log/Meta/Level.go @@ -3,14 +3,15 @@ package Meta type Level byte const ( - LevelWARN = Level(iota) - LevelDEBUG = Level(iota) - LevelERROR = Level(iota) - LevelINFO = Level(iota) - LevelTALKATIVE = Level(iota) - LevelSECURITY = Level(iota) + LevelWARN = Level(iota) // Level: Warning + LevelDEBUG = Level(iota) // Level: Debug + LevelERROR = Level(iota) // Level: Error + LevelINFO = Level(iota) // Level: Information + LevelTALKATIVE = Level(iota) // Level: Talkative (even more events as debug) + LevelSECURITY = Level(iota) // Level: Security ) +// Formats a level as string. func (lvl Level) Format() (result string) { switch lvl { case LevelDEBUG: @@ -32,6 +33,7 @@ func (lvl Level) Format() (result string) { return } +// Parse a level from a string. func ParseLevel(lvl string) (value Level) { switch lvl { case `L:DEBUG`: diff --git a/Log/Meta/MessageNames.go b/Log/Meta/MessageNames.go index 1a64940..d226a8b 100644 --- a/Log/Meta/MessageNames.go +++ b/Log/Meta/MessageNames.go @@ -2,37 +2,38 @@ package Meta type MessageName string +// Some pre-defined message names: const ( - MessageNameSTARTUP = `Startup` - MessageNameINIT = `Init` - MessageNameSHUTDOWN = `Shutdown` - MessageNameEXECUTE = `Execute` - MessageNameDATABASE = `Database` - MessageNameNETWORK = `Network` - MessageNameLOGIN = `Login` - MessageNameLOGOUT = `Logout` - MessageNameSESSION = `Session` - MessageNameTIMEOUT = `Timeout` - MessageNameFILESYSTEM = `Filesystem` - MessageNameCOMMUNICATION = `Communication` - MessageNameWRITE = `Write` - MessageNameREAD = `Read` - MessageNameALGORITHM = `Algorithm` - MessageNameCONFIGURATION = `Configuration` - MessageNameTIMER = `Timer` - MessageNameINPUT = `Input` - MessageNameOUTPUT = `Output` - MessageNameBROWSER = `Browser` - MessageNameSECURITY = `Security` - MessageNameNOTFOUND = `NotFound` - MessageNameANALYSIS = `Analysis` - MessageNameSTATE = `State` - MessageNameGENERATOR = `Generator` - MessageNamePRODUCER = `Producer` - MessageNameCONSUMER = `Consumer` - MessageNamePASSWORD = `Password` - MessageNamePARSE = `Parse` - MessageNameUSER = `User` - MessageNameREQUEST = `Request` - MessageNameRESPONSE = `Response` + MessageNameSTARTUP = `Startup` // e.g. the server startup + MessageNameINIT = `Init` // some kind of init event + MessageNameSHUTDOWN = `Shutdown` // some kind of shutdown event + MessageNameEXECUTE = `Execute` // some kind of execution context + MessageNameDATABASE = `Database` // events which are related to database issues + MessageNameNETWORK = `Network` // events which are related to network issues + MessageNameLOGIN = `Login` // some kind of login event + MessageNameLOGOUT = `Logout` // some kind of logout event + MessageNameSESSION = `Session` // some kind of session event + MessageNameTIMEOUT = `Timeout` // some kind of timeout event + MessageNameFILESYSTEM = `Filesystem` // events which are related to the filesystem + MessageNameCOMMUNICATION = `Communication` // events which are related to communication issues + MessageNameWRITE = `Write` // some kind of write event + MessageNameREAD = `Read` // some kind of read event + MessageNameALGORITHM = `Algorithm` // some kind of algorithm event + MessageNameCONFIGURATION = `Configuration` // some kind of configuration event + MessageNameTIMER = `Timer` // some kind of timer event + MessageNameINPUT = `Input` // some kind of input event + MessageNameOUTPUT = `Output` // some kind of output event + MessageNameBROWSER = `Browser` // some kind of browser event + MessageNameSECURITY = `Security` // some kind of security event + MessageNameNOTFOUND = `NotFound` // something was not found + MessageNameANALYSIS = `Analysis` // some kind of analysis event + MessageNameSTATE = `State` // some kind of state-related event + MessageNameGENERATOR = `Generator` // some kind of generator event + MessageNamePRODUCER = `Producer` // some kind of producer event + MessageNameCONSUMER = `Consumer` // some kind of consumer event + MessageNamePASSWORD = `Password` // some kind of password event + MessageNamePARSE = `Parse` // some kind of parser-related event + MessageNameUSER = `User` // some kind of user event + MessageNameREQUEST = `Request` // some kind of request-related event + MessageNameRESPONSE = `Response` // some kind of response-related event ) diff --git a/Log/Meta/Sender.go b/Log/Meta/Sender.go index c2328a9..43b1f69 100644 --- a/Log/Meta/Sender.go +++ b/Log/Meta/Sender.go @@ -1,3 +1,4 @@ package Meta +// Type for the sender name type Sender string diff --git a/Log/Meta/Severity.go b/Log/Meta/Severity.go index b07fbb6..1114e3e 100644 --- a/Log/Meta/Severity.go +++ b/Log/Meta/Severity.go @@ -3,14 +3,15 @@ package Meta type Severity byte const ( - SeverityNone = Severity(iota) - SeverityLow = Severity(iota) - SeverityMiddle = Severity(iota) - SeverityHigh = Severity(iota) - SeverityCritical = Severity(iota) - SeverityUnknown = Severity(iota) + SeverityNone = Severity(iota) // None severity + SeverityLow = Severity(iota) // Low severity + SeverityMiddle = Severity(iota) // Middle severity + SeverityHigh = Severity(iota) // High severity + SeverityCritical = Severity(iota) // Critical severity + SeverityUnknown = Severity(iota) // Unknown severity ) +// Format the severity as string. func (pri Severity) Format() (result string) { switch pri { case SeverityCritical: @@ -32,6 +33,7 @@ func (pri Severity) Format() (result string) { return } +// Parse the severity from a string. func ParseSeverity(pri string) (value Severity) { switch pri { case `S:CRITICAL`: diff --git a/Log/ReadProjectName.go b/Log/ReadProjectName.go new file mode 100644 index 0000000..2585529 --- /dev/null +++ b/Log/ReadProjectName.go @@ -0,0 +1,38 @@ +package Log + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" +) + +// Read the project name out of the local configuration file "project.name". +func readProjectName() { + // Try to get access to the working directory: + if currentDir, dirError := os.Getwd(); dirError != nil { + // Case: Error! Stop the server. + panic(`Cannot read the current working directory and therefore cannot read the project name!`) + } else { + // Try to get access to the file: + filename := filepath.Join(currentDir, `project.name`) + if _, errFile := os.Stat(filename); errFile != nil { + // Cases: Error. + if os.IsNotExist(errFile) { + panic(`Cannot read the project name file 'project.name': File not found!`) + } else { + panic(`Cannot read the project name file 'project.name': ` + errFile.Error()) + } + } + + // Try to read the file: + if projectNameBytes, errRead := ioutil.ReadFile(filename); errRead != nil { + // Case: Error. + panic(`Cannot read the project name file 'project.name': ` + errRead.Error()) + } else { + // Store the project name: + projectName = string(projectNameBytes) + projectName = strings.TrimSpace(projectName) + } + } +} diff --git a/Log/Scheduler.go b/Log/Scheduler.go index fcf2efb..ce93e85 100644 --- a/Log/Scheduler.go +++ b/Log/Scheduler.go @@ -5,21 +5,30 @@ import ( "time" ) -// Note: The scheduler is the consumer for the logging channel! +/* +The scheduler function which runs at a own thread. +Pleae note: The scheduler is the consumer for the logging channel. +*/ func scheduler(logBuffer chan Meta.Entry) { LogShort(senderName, Meta.CategorySYSTEM, Meta.LevelINFO, Meta.MessageNameSTARTUP, `The scheduler runs now.`) var stopNextTime = false + // Endless loop: for { + + // Enable the loop to stop: if stopNextTime { break } + // Read one entry from the buffer (channel): nextEntry, ok := <-logBuffer + // Case: The channel was closed. if !ok { - // The channel was closed! + + // Create a log message for this event. stopNextTime = true nextEntry = Meta.Entry{} nextEntry.Project = projectName @@ -33,9 +42,11 @@ func scheduler(logBuffer chan Meta.Entry) { nextEntry.MessageDescription = `The logging channel was closed!` } + // Queue the log event for the delivery to the devices: deviceDelay(nextEntry) } + // Exit the scheduler. Send the signal. LogFull(senderName, Meta.CategorySYSTEM, Meta.LevelWARN, Meta.SeverityCritical, Meta.ImpactNone, Meta.MessageNameSHUTDOWN, `The scheduler is down now.`) schedulerExitSignal <- true } diff --git a/Log/Timer.go b/Log/Timer.go index 4530fb7..fd932b4 100644 --- a/Log/Timer.go +++ b/Log/Timer.go @@ -9,6 +9,7 @@ import ( func initTimer() { + // Ensure that the timer runs only once: if timerIsRunning == true { LogFull(senderName, Meta.CategorySYSTEM, Meta.LevelWARN, Meta.SeverityHigh, Meta.ImpactNone, Meta.MessageNameSTARTUP, `The logging timer is already running.`) return @@ -17,23 +18,40 @@ func initTimer() { timerIsRunning = true LogShort(senderName, Meta.CategorySYSTEM, Meta.LevelINFO, Meta.MessageNameSTARTUP, `Create the logging timer now.`, fmt.Sprintf(`Timeout=%d seconds`, logDeviceDelayTimeoutSeconds)) + // Start the timer at a own thread: go func() { + // An endless loop: for { + + // Wait for the next run time: time.Sleep(time.Duration(logDeviceDelayTimeoutSeconds) * time.Second) + // Get exklusive access to the buffer: mutexDeviceDelays.Lock() + + // Read all the data from the device delay buffer: dataArray := logEntryListToArray(deviceDelayBuffer) + + // Re-init the buffer: deviceDelayBuffer.Init() + + // Release the lock to the buffer: mutexDeviceDelays.Unlock() + // Read-lock to read the devices list: mutexDevices.RLock() + + // For each logging device: for entry := devices.Front(); entry != nil; entry = entry.Next() { dev := entry.Value.(Device.Device) + + // Deliver the current logging events with an extra thread: go dev.Log(dataArray) } - mutexDevices.RUnlock() + // Release the read-lock: + mutexDevices.RUnlock() } }() } diff --git a/Log/Variables.go b/Log/Variables.go index 018697f..7601c2d 100644 --- a/Log/Variables.go +++ b/Log/Variables.go @@ -7,22 +7,22 @@ import ( ) var ( - entriesBuffer chan Meta.Entry = nil - schedulerExitSignal chan bool = nil - logBufferSize int = 500 - logBufferTimeoutSeconds int = 4 - logDeviceDelayNumberEvents int = 600 - logDeviceDelayTimeoutSeconds int = 5 - channelReady bool = false - preChannelBufferUsed bool = false - preChannelBuffer *list.List = nil - deviceDelayBuffer *list.List = nil - devices *list.List = nil - mutexDeviceDelays sync.Mutex = sync.Mutex{} - mutexPreChannelBuffer sync.Mutex = sync.Mutex{} - mutexChannel sync.RWMutex = sync.RWMutex{} - mutexDevices sync.RWMutex = sync.RWMutex{} - timerIsRunning bool = false - projectName string = `not set` - senderName Meta.Sender = `System::Log` + entriesBuffer chan Meta.Entry = nil // The channel / buffer for new log entries + schedulerExitSignal chan bool = nil // Exit signal for the scheduler + logBufferSize int = 500 // Buffer size for the logging + logBufferTimeoutSeconds int = 4 // Timeout for the logging + logDeviceDelayNumberEvents int = 600 // Delay of # of events for the devices + logDeviceDelayTimeoutSeconds int = 5 // Timeout for the logging devices + channelReady bool = false // State of the channel + preChannelBufferUsed bool = false // State of the logging (pre or ready?) + preChannelBuffer *list.List = nil // Extra buffer for the pre logging phase + deviceDelayBuffer *list.List = nil // Buffer for the batch write to the devices + devices *list.List = nil // List of all devices + mutexDeviceDelays sync.Mutex = sync.Mutex{} // Mutex for buffer + mutexPreChannelBuffer sync.Mutex = sync.Mutex{} // Mutex for buffer + mutexChannel sync.RWMutex = sync.RWMutex{} // Mutex for the main channel + mutexDevices sync.RWMutex = sync.RWMutex{} // Mutex for the devices + timerIsRunning bool = false // Status of timer + projectName string = `not set` // The project name for the logging + senderName Meta.Sender = `System::Log` // This is the name for logging event from this package ) diff --git a/Log/Web/HandlerCommons.go b/Log/Web/HandlerCommons.go index a43d079..61c5082 100644 --- a/Log/Web/HandlerCommons.go +++ b/Log/Web/HandlerCommons.go @@ -8,6 +8,7 @@ import ( "net/http" ) +// Handler for some CSS data for the web logger. func HandlerCSSNormalize(response http.ResponseWriter, request *http.Request) { if Shutdown.IsDown() { @@ -19,6 +20,7 @@ func HandlerCSSNormalize(response http.ResponseWriter, request *http.Request) { fmt.Fprint(response, Assets.CSSNormalize) } +// Handler for some CSS data for the web logger. func HandlerCSSWebflow(response http.ResponseWriter, request *http.Request) { if Shutdown.IsDown() { @@ -30,6 +32,7 @@ func HandlerCSSWebflow(response http.ResponseWriter, request *http.Request) { fmt.Fprint(response, Assets.CSSWebflow) } +// Handler for some CSS data for the web logger. func HandlerCSSLog(response http.ResponseWriter, request *http.Request) { if Shutdown.IsDown() { @@ -41,6 +44,7 @@ func HandlerCSSLog(response http.ResponseWriter, request *http.Request) { fmt.Fprint(response, Assets.CSSLog) } +// Handler for some JS for the web logger. func HandlerJSModernizr(response http.ResponseWriter, request *http.Request) { if Shutdown.IsDown() { @@ -52,6 +56,7 @@ func HandlerJSModernizr(response http.ResponseWriter, request *http.Request) { fmt.Fprint(response, Assets.JSModernizr) } +// Handler for some JS for the web logger. func HandlerJSWebflow(response http.ResponseWriter, request *http.Request) { if Shutdown.IsDown() { @@ -63,6 +68,7 @@ func HandlerJSWebflow(response http.ResponseWriter, request *http.Request) { fmt.Fprint(response, Assets.JSWebflow) } +// Handler for some JS for the web logger. func HandlerJSjQuery(response http.ResponseWriter, request *http.Request) { if Shutdown.IsDown() { @@ -74,6 +80,7 @@ func HandlerJSjQuery(response http.ResponseWriter, request *http.Request) { fmt.Fprint(response, Assets.JSjQuery) } +// Handler for some JS for the web logger. func HandlerJSjQueryMap(response http.ResponseWriter, request *http.Request) { if Shutdown.IsDown() { diff --git a/Log/Web/HandlerLog.go b/Log/Web/HandlerLog.go index d425fbc..7f73c50 100644 --- a/Log/Web/HandlerLog.go +++ b/Log/Web/HandlerLog.go @@ -11,30 +11,32 @@ import ( "strings" ) +// Handler for accessing the web logging. func HandlerWebLog(response http.ResponseWriter, request *http.Request) { + // Case: The system goes down now. if Shutdown.IsDown() { http.NotFound(response, request) return } + // Execute the HTTP form: request.ParseForm() countParameters := len(request.Form) + // Setup the data for the HTML template: data := Scheme.Viewer{} data.Title = `Web Log Viewer` data.Sender = DeviceDatabase.ReadSenderNames() data.MessageNames = DeviceDatabase.ReadMessageNames() + // To less parameters? if countParameters < 9 { - // Initial view => refresh & first page (latest logs) data.Events = readLatest() data.SetLiveView = true - } else { - - // Custom view + // Case: Custom view currentLevel := request.FormValue(`Level`) currentTimeRange := request.FormValue(`timeRange`) currentCategory := request.FormValue(`Category`) @@ -45,6 +47,7 @@ func HandlerWebLog(response http.ResponseWriter, request *http.Request) { currentPage := request.FormValue(`CurrentPage`) currentLiveView := request.FormValue(`LiveView`) + // Store the events for the template: data.Events = readCustom(currentTimeRange, currentLevel, currentCategory, currentImpact, currentSeverity, currentMessageName, currentSender, currentPage) if strings.ToLower(currentLiveView) == `true` { @@ -94,6 +97,7 @@ func HandlerWebLog(response http.ResponseWriter, request *http.Request) { } } + // Write the MIME type and execute the template: MimeTypes.Write2HTTP(response, MimeTypes.TypeWebHTML) if executeError := templates.ExecuteTemplate(response, `WebLog`, data); executeError != nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameEXECUTE, `Was not able to execute the web log viewer template.`, executeError.Error()) diff --git a/Log/Web/Init.go b/Log/Web/Init.go index f991931..381e607 100644 --- a/Log/Web/Init.go +++ b/Log/Web/Init.go @@ -7,11 +7,13 @@ import ( "html/template" ) +// The init function for this package. func init() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Init the web log.`) defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Init the web log done.`) + // Create the cache of all web logging templates: templates = template.New(`root`) if _, err := templates.Parse(WebTemp.Viewer); err != nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to parse the template for the web log viewer.`, err.Error()) diff --git a/Log/Web/ReadCustom.go b/Log/Web/ReadCustom.go index fec3b43..acaae7d 100644 --- a/Log/Web/ReadCustom.go +++ b/Log/Web/ReadCustom.go @@ -7,18 +7,24 @@ import ( "strings" ) +// Read a custom event range from the database. func readCustom(timeRange, logLevel, logCategory, logImpact, logSeverity, logMessageName, logSender, logPage string) (events []Scheme.LogEvent) { + // Get the custom events: eventsFromDB := DeviceDatabase.ReadCustom(timeRange, logLevel, logCategory, logImpact, logSeverity, logMessageName, logSender, logPage) count := len(eventsFromDB) + + // Array with all events, prepared for the website: events = make([]Scheme.LogEvent, count) + // Copy each event to the right format: for n := 0; n < count; n++ { eventFromDB := eventsFromDB[n] events[n] = Scheme.LogEvent{} events[n].LogLine = eventFromDB.Format() events[n].LogLevel = fmt.Sprintf("log%s", strings.ToLower(eventFromDB.Level[2:])) + // Vary the color of each line: if n%2 == 0 { events[n].AB = Scheme.B } else { diff --git a/Log/Web/ReadLatest.go b/Log/Web/ReadLatest.go index c1e5421..df3cf1c 100644 --- a/Log/Web/ReadLatest.go +++ b/Log/Web/ReadLatest.go @@ -7,18 +7,23 @@ import ( "strings" ) +// Read the latest log events from the database func readLatest() (events []Scheme.LogEvent) { - + // Get the latest events from the database eventsFromDB := DeviceDatabase.ReadLatest() count := len(eventsFromDB) + + // Array for the log events, prepared for the website: events = make([]Scheme.LogEvent, count) + // Copy each event to the right format for the website: for n := 0; n < count; n++ { eventFromDB := eventsFromDB[n] events[n] = Scheme.LogEvent{} events[n].LogLine = eventFromDB.Format() events[n].LogLevel = fmt.Sprintf("log%s", strings.ToLower(eventFromDB.Level[2:])) + // Vary the color of each line: if n%2 == 0 { events[n].AB = Scheme.B } else { diff --git a/Log/Web/Scheme/Constants.go b/Log/Web/Scheme/Constants.go index 0461a0b..f15dc7a 100644 --- a/Log/Web/Scheme/Constants.go +++ b/Log/Web/Scheme/Constants.go @@ -1,6 +1,6 @@ package Scheme const ( - A string = `loga` - B string = `logb` + A string = `loga` // Web log line, kind A (different color for each line) + B string = `logb` // Web log line, kind B (different color for each line) ) diff --git a/Log/Web/Scheme/Scheme.go b/Log/Web/Scheme/Scheme.go index f0c16c4..e2dab8b 100644 --- a/Log/Web/Scheme/Scheme.go +++ b/Log/Web/Scheme/Scheme.go @@ -1,5 +1,6 @@ package Scheme +// The type for the web logger viewer template type Viewer struct { Title string SetLiveView bool @@ -16,12 +17,7 @@ type Viewer struct { Events []LogEvent } -//
  • -//
    ....
    -//
  • -//
  • -//
    ....
    -//
  • +// Type for a log event type LogEvent struct { LogLine string LogLevel string // logwarn || logdebug || logerror || loginfo || logtalkative || logsecurity diff --git a/Log/Web/Templates/Viewer.go b/Log/Web/Templates/Viewer.go index 91dda37..85a6948 100644 --- a/Log/Web/Templates/Viewer.go +++ b/Log/Web/Templates/Viewer.go @@ -1,5 +1,6 @@ package Templates +// The template for the web log viewer: var Viewer string = ` {{define "WebLog"}} diff --git a/Log/Web/Variables.go b/Log/Web/Variables.go index ef1f76e..d1538be 100644 --- a/Log/Web/Variables.go +++ b/Log/Web/Variables.go @@ -6,6 +6,6 @@ import ( ) var ( - templates *template.Template = nil - senderName LM.Sender = `System::WebLog` + templates *template.Template = nil // The web logging templates + senderName LM.Sender = `System::WebLog` // This is the name for logging event from this package ) diff --git a/Main.go b/Main.go index 2cb3c9d..641cf1d 100644 --- a/Main.go +++ b/Main.go @@ -6,6 +6,11 @@ import ( "github.com/SommerEngineering/Ocean/System" ) +/* +This is the entry point of Ocean in case of using it as e.g. messaging broker +or logging service, etc. This function does not matter if Ocean is used as +framework. +*/ func main() { Log.LogShort(senderName, LM.CategoryAPP, LM.LevelINFO, LM.MessageNameSTARTUP, `Ocean is starting.`) System.InitHandlers() diff --git a/MimeTypes/MimeTypes.go b/MimeTypes/DetectType.go similarity index 77% rename from MimeTypes/MimeTypes.go rename to MimeTypes/DetectType.go index 4250f5e..ea274ce 100644 --- a/MimeTypes/MimeTypes.go +++ b/MimeTypes/DetectType.go @@ -1,24 +1,20 @@ -package MimeTypes - -import ( - "strings" -) - -type MimeType struct { - MimeType string - FileExtension []string -} - -func DetectType(filename string) (mime MimeType, err error) { - for _, typeElement := range allTypes { - for _, extension := range typeElement.FileExtension { - if strings.HasSuffix(filename, extension) { - mime = typeElement - return - } - } - } - - mime = TypeUnknown - return -} +package MimeTypes + +import ( + "strings" +) + +// A function to detect a MIME type. +func DetectType(filename string) (mime MimeType, err error) { + for _, typeElement := range allTypes { + for _, extension := range typeElement.FileExtension { + if strings.HasSuffix(filename, extension) { + mime = typeElement + return + } + } + } + + mime = TypeUnknown + return +} diff --git a/MimeTypes/Init.go b/MimeTypes/Init.go index 02edb53..308555f 100644 --- a/MimeTypes/Init.go +++ b/MimeTypes/Init.go @@ -1,8 +1,9 @@ package MimeTypes -var allTypes [32]MimeType - +// The init function for this package. func init() { + + // Store the instances for all known types: allTypes[0] = TypeWebHTML allTypes[1] = TypeWebCSS allTypes[2] = TypeWebJavaScript @@ -35,4 +36,6 @@ func init() { allTypes[29] = TypeFontTTF allTypes[30] = TypeFontWOFF allTypes[31] = TypeWebJSON + allTypes[32] = TypeCSV + allTypes[33] = TypeWebDart } diff --git a/MimeTypes/KnownTypes.go b/MimeTypes/KnownTypes.go deleted file mode 100644 index 95d9a1e..0000000 --- a/MimeTypes/KnownTypes.go +++ /dev/null @@ -1,36 +0,0 @@ -package MimeTypes - -var TypeWebHTML = MimeType{MimeType: "text/html", FileExtension: []string{".html", ".htm"}} -var TypeWebCSS = MimeType{MimeType: "text/css", FileExtension: []string{".css"}} -var TypeWebJavaScript = MimeType{MimeType: "text/javascript", FileExtension: []string{".js"}} -var TypeWebDart = MimeType{MimeType: "application/dart", FileExtension: []string{".dart"}} -var TypeXML = MimeType{MimeType: "text/xml", FileExtension: []string{".xml"}} -var TypeArchiveZIP = MimeType{MimeType: "application/zip", FileExtension: []string{".zip"}} -var TypeArchiveGZ = MimeType{MimeType: "application/gzip", FileExtension: []string{".gz"}} -var TypeWebOCTET = MimeType{MimeType: "application/octet-stream", FileExtension: []string{".bin", ".exe", ".dll", ".class"}} -var TypeDocumentPDF = MimeType{MimeType: "application/pdf", FileExtension: []string{".pdf"}} -var TypeDocumentLaTeX = MimeType{MimeType: "application/x-latex", FileExtension: []string{".tex", ".latex"}} -var TypeShockwave = MimeType{MimeType: "application/x-shockwave-flash", FileExtension: []string{".swf"}} -var TypeArchiveTAR = MimeType{MimeType: "application/x-tar", FileExtension: []string{".tar"}} -var TypeAudioWAV = MimeType{MimeType: "application/x-wav", FileExtension: []string{".wav"}} -var TypeAudioMP3 = MimeType{MimeType: "audio/mpeg", FileExtension: []string{".mp3"}} -var TypeAudioAAC = MimeType{MimeType: "audio/aac", FileExtension: []string{".aac", ".m4a"}} -var TypeAudioOGG = MimeType{MimeType: "audio/ogg", FileExtension: []string{"vogg", ".oga"}} -var TypeAudioWMA = MimeType{MimeType: "audio/x-ms-wma", FileExtension: []string{".wma"}} -var TypeImageGIF = MimeType{MimeType: "image/gif", FileExtension: []string{".gif"}} -var TypeImageCommon = MimeType{MimeType: "image", FileExtension: []string{}} -var TypeUnknown = MimeType{MimeType: "application/octet-stream", FileExtension: []string{}} -var TypeImageJPEG = MimeType{MimeType: "image/jpeg", FileExtension: []string{".jpg", ".jpeg"}} -var TypeImagePNG = MimeType{MimeType: "image/png", FileExtension: []string{".png"}} -var TypePlainText = MimeType{MimeType: "text/plain", FileExtension: []string{".txt"}} -var TypeVideoMPEG = MimeType{MimeType: "video/mpeg", FileExtension: []string{".mpeg", ".mpg"}} -var TypeVideoMOV = MimeType{MimeType: "video/quicktime", FileExtension: []string{".mov", ".qt"}} -var TypeVideoAVI = MimeType{MimeType: "video/x-msvideo", FileExtension: []string{".avi"}} -var TypeVideoMP4 = MimeType{MimeType: "video/mp4", FileExtension: []string{".mp4"}} -var TypeFontEOT = MimeType{MimeType: "application/vnd.ms-fontobject", FileExtension: []string{".eot"}} -var TypeFontOTF = MimeType{MimeType: "application/font-sfnt", FileExtension: []string{".otf"}} -var TypeImageSVG = MimeType{MimeType: "image/svg+xml", FileExtension: []string{".svg"}} -var TypeFontTTF = MimeType{MimeType: "application/font-sfnt", FileExtension: []string{".ttf"}} -var TypeFontWOFF = MimeType{MimeType: "application/font-woff", FileExtension: []string{".woff"}} -var TypeWebJSON = MimeType{MimeType: "application/json", FileExtension: []string{".json"}} -var TypeCSV = MimeType{MimeType: "text/csv", FileExtension: []string{".csv"}} diff --git a/MimeTypes/Scheme.go b/MimeTypes/Scheme.go new file mode 100644 index 0000000..c9aa13f --- /dev/null +++ b/MimeTypes/Scheme.go @@ -0,0 +1,7 @@ +package MimeTypes + +// The type for a MIME type. +type MimeType struct { + MimeType string + FileExtension []string +} diff --git a/MimeTypes/Variables.go b/MimeTypes/Variables.go new file mode 100644 index 0000000..e2a0a17 --- /dev/null +++ b/MimeTypes/Variables.go @@ -0,0 +1,41 @@ +package MimeTypes + +var ( + allTypes [34]MimeType // Array for all known types + + // Create instances for each known types: + TypeWebHTML = MimeType{MimeType: "text/html", FileExtension: []string{".html", ".htm"}} + TypeWebCSS = MimeType{MimeType: "text/css", FileExtension: []string{".css"}} + TypeWebJavaScript = MimeType{MimeType: "text/javascript", FileExtension: []string{".js"}} + TypeWebDart = MimeType{MimeType: "application/dart", FileExtension: []string{".dart"}} + TypeXML = MimeType{MimeType: "text/xml", FileExtension: []string{".xml"}} + TypeArchiveZIP = MimeType{MimeType: "application/zip", FileExtension: []string{".zip"}} + TypeArchiveGZ = MimeType{MimeType: "application/gzip", FileExtension: []string{".gz"}} + TypeWebOCTET = MimeType{MimeType: "application/octet-stream", FileExtension: []string{".bin", ".exe", ".dll", ".class"}} + TypeDocumentPDF = MimeType{MimeType: "application/pdf", FileExtension: []string{".pdf"}} + TypeDocumentLaTeX = MimeType{MimeType: "application/x-latex", FileExtension: []string{".tex", ".latex"}} + TypeShockwave = MimeType{MimeType: "application/x-shockwave-flash", FileExtension: []string{".swf"}} + TypeArchiveTAR = MimeType{MimeType: "application/x-tar", FileExtension: []string{".tar"}} + TypeAudioWAV = MimeType{MimeType: "application/x-wav", FileExtension: []string{".wav"}} + TypeAudioMP3 = MimeType{MimeType: "audio/mpeg", FileExtension: []string{".mp3"}} + TypeAudioAAC = MimeType{MimeType: "audio/aac", FileExtension: []string{".aac", ".m4a"}} + TypeAudioOGG = MimeType{MimeType: "audio/ogg", FileExtension: []string{"vogg", ".oga"}} + TypeAudioWMA = MimeType{MimeType: "audio/x-ms-wma", FileExtension: []string{".wma"}} + TypeImageGIF = MimeType{MimeType: "image/gif", FileExtension: []string{".gif"}} + TypeImageCommon = MimeType{MimeType: "image", FileExtension: []string{}} + TypeUnknown = MimeType{MimeType: "application/octet-stream", FileExtension: []string{}} + TypeImageJPEG = MimeType{MimeType: "image/jpeg", FileExtension: []string{".jpg", ".jpeg"}} + TypeImagePNG = MimeType{MimeType: "image/png", FileExtension: []string{".png"}} + TypePlainText = MimeType{MimeType: "text/plain", FileExtension: []string{".txt"}} + TypeVideoMPEG = MimeType{MimeType: "video/mpeg", FileExtension: []string{".mpeg", ".mpg"}} + TypeVideoMOV = MimeType{MimeType: "video/quicktime", FileExtension: []string{".mov", ".qt"}} + TypeVideoAVI = MimeType{MimeType: "video/x-msvideo", FileExtension: []string{".avi"}} + TypeVideoMP4 = MimeType{MimeType: "video/mp4", FileExtension: []string{".mp4"}} + TypeFontEOT = MimeType{MimeType: "application/vnd.ms-fontobject", FileExtension: []string{".eot"}} + TypeFontOTF = MimeType{MimeType: "application/font-sfnt", FileExtension: []string{".otf"}} + TypeImageSVG = MimeType{MimeType: "image/svg+xml", FileExtension: []string{".svg"}} + TypeFontTTF = MimeType{MimeType: "application/font-sfnt", FileExtension: []string{".ttf"}} + TypeFontWOFF = MimeType{MimeType: "application/font-woff", FileExtension: []string{".woff"}} + TypeWebJSON = MimeType{MimeType: "application/json", FileExtension: []string{".json"}} + TypeCSV = MimeType{MimeType: "text/csv", FileExtension: []string{".csv"}} +) diff --git a/MimeTypes/Write2HTTP.go b/MimeTypes/Write2HTTP.go index 939f743..c22f9d2 100644 --- a/MimeTypes/Write2HTTP.go +++ b/MimeTypes/Write2HTTP.go @@ -4,6 +4,7 @@ import ( "net/http" ) +// Function to write a MIME type to a client. func Write2HTTP(response http.ResponseWriter, mime MimeType) { response.Header().Add(`Content-Type`, mime.MimeType) } diff --git a/NumGen/Init.go b/NumGen/Init.go index ba09a05..d93585e 100644 --- a/NumGen/Init.go +++ b/NumGen/Init.go @@ -9,9 +9,11 @@ import ( "strings" ) +// Init this package. func init() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Init the number generator.`) + // Get the exklusive access to the channel list: channelListLock.Lock() defer channelListLock.Unlock() diff --git a/NumGen/Scheme.go b/NumGen/Scheme.go index 7ee7c58..3f60c58 100644 --- a/NumGen/Scheme.go +++ b/NumGen/Scheme.go @@ -1,6 +1,7 @@ package NumGen +// The scheme for the database. type NumberGenScheme struct { - Name string `bson:"Name"` - NextFreeNumber int64 `bson:"NextFreeNumber"` + Name string `bson:"Name"` // A name for this counter. + NextFreeNumber int64 `bson:"NextFreeNumber"` // The next number. } diff --git a/NumGen/Variables.go b/NumGen/Variables.go index 31cdbe3..90a8332 100644 --- a/NumGen/Variables.go +++ b/NumGen/Variables.go @@ -7,7 +7,9 @@ import ( ) var ( - correctPassword string = `` + correctPassword string = `` + + // This is the name for logging event from this package: senderName LM.Sender = `System::NumGen::Producer` isActive bool = false getHandler string = `` diff --git a/README.md b/README.md index 52a1708..ad7509b 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ Ocean is a smart and powerful application framework and server which uses the KI * A public facing webserver * A private facing webserver (e.g. for administration issues) * A messaging component called ICCC to communicate with all of your servers (or even with other parts at different programming languages at different servers) -* A template engine for web applications -* A half-automated configuration management +* A distributed template engine for web applications +* A distributed half-automated configuration management * A simple I18N support * A simple database abstraction which MongoDB as database back-end @@ -19,3 +19,11 @@ Ocean is a smart and powerful application framework and server which uses the KI ## Stability Several smaller and a few bigger projects using already Ocean at production level. The experiences are very good: The projects are running fine over several months - normally, the server maintenance with system reboots happens before any issue occur. A long-term project for social media monitoring runs on a specially prepared server without maintenance window since more than 9 months without any restart and without any related issue. Other projects are running across multiple servers with automatic load balancing and fail-over (load balancing and fail-over are provided by Amazon AWS, and not yet by Ocean). + +## Environment Requirements +You have to setup a MongoDB (http://www.mongodb.org) database or database cluster. You can setup individual databases for the configuration management, for the customer data and for the logging service. It is also possible to use one database for all! In case of the logging database, you can also use one shared logging database for any count of Ocean projects. The databases may run on different dedicated servers, if you want. + +Ocean uses a very small memory footprint between xxx and 20 MB, thus, the memory requirements are very low. Further, Ocean also tries to avoid any disk I/O after start-up and utilise for e.g. HTML templates and small static files an in-memory cache. + +## Thanks +"github.com/twinj/uuid" \ No newline at end of file diff --git a/Robots/Handler.go b/Robots/Handler.go index 2ea8db8..8621f63 100644 --- a/Robots/Handler.go +++ b/Robots/Handler.go @@ -8,7 +8,10 @@ import ( "net/http" ) +// Handler for the access to the robots.txt. func HandlerRobots(response http.ResponseWriter, request *http.Request) { + + // Case: The system goes down now? if Shutdown.IsDown() { http.NotFound(response, request) return diff --git a/Robots/Init.go b/Robots/Init.go index fdd0f51..5677611 100644 --- a/Robots/Init.go +++ b/Robots/Init.go @@ -6,7 +6,10 @@ import ( LM "github.com/SommerEngineering/Ocean/Log/Meta" ) +// Init the package. func init() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Init the robots component.`) + + // Read the robots.txt from the database: robotsContent = ConfigurationDB.Read(`robots.txt`) } diff --git a/Robots/Variables.go b/Robots/Variables.go index 235bbc3..8a73e17 100644 --- a/Robots/Variables.go +++ b/Robots/Variables.go @@ -5,7 +5,7 @@ import ( ) var ( - senderName LM.Sender = `System::Robots` + senderName LM.Sender = `System::Robots` // This is the name for logging event from this package robotsContent string = `User-agent: * Disallow: /` ) diff --git a/Shutdown/Check.go b/Shutdown/Check.go index 41641db..7cb7f2a 100644 --- a/Shutdown/Check.go +++ b/Shutdown/Check.go @@ -1,5 +1,6 @@ package Shutdown +// A function to check if the system goes down right now. func IsDown() (result bool) { result = stopAllRequests return diff --git a/Shutdown/Init.go b/Shutdown/Init.go index 6d3a42b..dced552 100644 --- a/Shutdown/Init.go +++ b/Shutdown/Init.go @@ -6,13 +6,17 @@ import ( "os/signal" ) +// Init the package. func init() { shutdownHandlers = list.New() } +// The manual init for the shutdown notify handler. func InitShutdown() { // Apply the shutdown handler: signal.Notify(shutdownSignal, os.Interrupt, os.Kill) + + // Start a new thread which waits for the shutdown event: go executeShutdown() } diff --git a/Shutdown/Shutdown.go b/Shutdown/Shutdown.go index 7f321a2..acfb65f 100644 --- a/Shutdown/Shutdown.go +++ b/Shutdown/Shutdown.go @@ -8,28 +8,38 @@ import ( "os" ) +// A type for shutdown handlers. type ShutdownHandler interface { Shutdown() } +// Function to add new shutdown handlers. func AddShutdownHandler(handler ShutdownHandler) { shutdownHandlers.PushFront(handler) } +// The thread which waits for the shutdown event. func executeShutdown() { + + // Wait for the shutdown event: sig := <-shutdownSignal stopAllRequests = true - Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameSHUTDOWN, `The system was called to shutting down.`, sig.String(), `Call now all shutdown handlers.`) + + // Execute all shutdown handlers: for handler := shutdownHandlers.Front(); handler != nil; handler = handler.Next() { safeCall(handler) } + // Shutdown the logging system: Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameSHUTDOWN, `The system is shutting down now.`) Log.Flush() + + // Stop the whole server: os.Exit(0) } +// This function is a wrapper to call safely a shutdown handler. func safeCall(handler *list.Element) { defer func() { if err := recover(); err != nil { diff --git a/Shutdown/Variables.go b/Shutdown/Variables.go index 1ecdafa..9c241fd 100644 --- a/Shutdown/Variables.go +++ b/Shutdown/Variables.go @@ -7,8 +7,8 @@ import ( ) var ( - shutdownSignal chan os.Signal = make(chan os.Signal) - shutdownHandlers *list.List = nil - senderName LM.Sender = `System::Shutdown` - stopAllRequests bool = false + shutdownSignal chan os.Signal = make(chan os.Signal) // A channel for the shutdown event + shutdownHandlers *list.List = nil // All shutdown handlers + senderName LM.Sender = `System::Shutdown` // This is the name for logging event from this package + stopAllRequests bool = false // Does the system goes down now? ) diff --git a/StaticFiles/FindAndReadFile.go b/StaticFiles/FindAndReadFile.go index a734f32..d9b3421 100644 --- a/StaticFiles/FindAndReadFile.go +++ b/StaticFiles/FindAndReadFile.go @@ -9,7 +9,10 @@ import ( "io/ioutil" ) +// Try to read a static file. func FindAndReadFile(filename string) (result []byte) { + + // Case: The system goes down. if Shutdown.IsDown() { return } @@ -24,13 +27,17 @@ func FindAndReadFile(filename string) (result []byte) { return } + // Loop over all files inside the ZIP file: for _, file := range reader.File { + + // Is this the desired file? if file.Name == path { + // Open the file: fileReader, openError := file.Open() defer fileReader.Close() - if openError == nil { + // Read all the content: contentData, readError := ioutil.ReadAll(fileReader) if readError != nil { diff --git a/StaticFiles/Handler.go b/StaticFiles/Handler.go index 248f724..3dd3408 100644 --- a/StaticFiles/Handler.go +++ b/StaticFiles/Handler.go @@ -10,13 +10,16 @@ import ( "strings" ) +// Handler to deliver static files. func HandlerStaticFiles(response http.ResponseWriter, request *http.Request) { + + // Case: The system goes down. if Shutdown.IsDown() { http.NotFound(response, request) return } - // Prepare the path: + // Prepare the path by removing the prefix: path := strings.Replace(request.RequestURI, `/staticFiles/`, ``, 1) path = strings.Replace(path, `%20`, ` `, -1) fileType := `` @@ -30,24 +33,30 @@ func HandlerStaticFiles(response http.ResponseWriter, request *http.Request) { fileType = mimeType.MimeType } + // Case: Was not able to determine the file's type. if fileType == `` { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameNOTFOUND, `The mime type is unknown.`, path) http.NotFound(response, request) return } + // Read the file's content: contentData := FindAndReadFile(path) + + // Case: Was not able to read the file. if contentData == nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `The desired file was not found.`, path) http.NotFound(response, request) return } + // Send the meta data and the content to the client: fileLenText := fmt.Sprintf(`%d`, len(contentData)) response.Header().Add(`Content-Length`, fileLenText) response.Header().Add(`Content-Type`, fileType) response.Write(contentData) + // Log all static files accesses? if logStaticFileRequests { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameBROWSER, `A static file was requested.`, path) } diff --git a/StaticFiles/Init.go b/StaticFiles/Init.go index 2df69ea..e72f8ec 100644 --- a/StaticFiles/Init.go +++ b/StaticFiles/Init.go @@ -8,15 +8,18 @@ import ( "io/ioutil" ) +// The init of the static file package. func init() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Starting now the static files component.`) defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Starting the static files component done.`) + // If the static files are disabled, stop here: if ConfigurationDB.Read(`EnableStaticFiles`) != `true` { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Static files are disabled.`) return } + // Case: Static files are enabled. Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Static files are enabled.`) // Read the configuration: @@ -27,7 +30,7 @@ func init() { logStaticFileRequests = ConfigurationDB.Read(`LogStaticFileRequests`) == `true` - // Read the static files' data from GridFS: + // Read the static files' data from GridFS and keep it in-memory: dbSession, gridFS := CustomerDB.GridFS() defer dbSession.Close() diff --git a/StaticFiles/Map2Root.go b/StaticFiles/Map2Root.go index d64ac9c..e1b1304 100644 --- a/StaticFiles/Map2Root.go +++ b/StaticFiles/Map2Root.go @@ -5,7 +5,10 @@ import ( "net/http" ) +// Handler to map the static files to the root. Use it for static web sites. func HandlerMapStaticFiles2Root(response http.ResponseWriter, request *http.Request) { + + // Case: The system goes down. if Shutdown.IsDown() { http.NotFound(response, request) return diff --git a/StaticFiles/Variables.go b/StaticFiles/Variables.go index 01b3a89..e071814 100644 --- a/StaticFiles/Variables.go +++ b/StaticFiles/Variables.go @@ -5,8 +5,8 @@ import ( ) var ( - senderName LM.Sender = `System::StaticFiles` - startFile4Map2Root string = `index.html` - logStaticFileRequests bool = false - zipData []byte = nil + senderName LM.Sender = `System::StaticFiles` // This is the name for logging event from this package + startFile4Map2Root string = `index.html` // The default filename in case of mapping static files to the root + logStaticFileRequests bool = false // Logging each access? + zipData []byte = nil // The in-memory cache of the data ) diff --git a/System/ICCCStart.go b/System/ICCCStart.go index 5b5bd91..3df5d9d 100644 --- a/System/ICCCStart.go +++ b/System/ICCCStart.go @@ -7,8 +7,15 @@ import ( LM "github.com/SommerEngineering/Ocean/Log/Meta" ) +// The receiver function for the ICCC message, that a server is up and running. func icccSystemStart(data map[string][]string) { + + // Converts the HTTP form data into an object: _, _, obj := ICCC.Data2Message(&SystemMessages.ICCCStartUpMessage{}, data) + + // Cast the object to the right type: messageData := obj.(*SystemMessages.ICCCStartUpMessage) + + // Provide a log entry: Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `ICCC message: The server is now up and ready.`, messageData.PublicIPAddressAndPort, messageData.AdminIPAddressAndPort) } diff --git a/System/Init.go b/System/Init.go index 526f4f9..b8f8a10 100644 --- a/System/Init.go +++ b/System/Init.go @@ -13,18 +13,21 @@ import ( "strconv" ) +// Init the system. func initSystem() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `The system is now starting.`) - // Set the desired amount of CPUs: + // Set the desired amount of CPUs to use by Ocean. Default: 2 utilizeCPUs := 2 if value, err := strconv.Atoi(ConfigurationDB.Read(`OceanUtilizeCPUs`)); err != nil { + // Case: Error! Use the default. Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `Was not able to read the OceanUtilizeCPUs configuration.`, `Use the default value instead.`) } else { utilizeCPUs = value } + // Set the amount of CPUs: runtime.GOMAXPROCS(utilizeCPUs) Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `Configuration OceanUtilizeCPUs is set.`, fmt.Sprintf(`value=%d`, utilizeCPUs)) diff --git a/System/InitHandlers.go b/System/InitHandlers.go index fa86bb4..a66a393 100644 --- a/System/InitHandlers.go +++ b/System/InitHandlers.go @@ -14,25 +14,54 @@ import ( "github.com/SommerEngineering/Ocean/WebContent" ) +// Init the system and then the system's handlers. func InitHandlers() { initSystem() Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Register now all system handlers.`) + // // Public Handlers: + // + + // Handler for the web frameworks like e.g. jQuery, Bootstrap, etc. Handlers.AddPublicHandler(`/framework/`, WebContent.HandlerDeliverFramework) + + // Handler for other static files: Handlers.AddPublicHandler(`/staticFiles/`, StaticFiles.HandlerStaticFiles) + + // Handler for the number generator: Handlers.AddPublicHandler(`/next/number`, NumGen.HandlerGetNext) + + // Handler for the robots.txt: Handlers.AddPublicHandler(`/robots.txt`, Robots.HandlerRobots) + + // Handler for the ICCC to the public: Handlers.AddPublicHandler(`/ICCC`, ICCC.ICCCHandler) + // // Private Handlers: + // + + // Handler for the web frameworks like e.g. jQuery, Bootstrap, etc. Handlers.AddAdminHandler(`/framework/`, WebContent.HandlerDeliverFramework) + + // Handler for other static files: Handlers.AddAdminHandler(`/staticFiles/`, StaticFiles.HandlerStaticFiles) + + // Handler for the number generator: Handlers.AddAdminHandler(`/next/number`, NumGen.HandlerGetNext) + + // Handler for the ICCC to the private side: Handlers.AddAdminHandler(`/ICCC`, ICCC.ICCCHandler) + + // Handler for binary assets, used for the admin pages: Handlers.AddAdminHandler(`/binaryAssets/`, BinaryAssets.HandlerBinaryAssets) + + // Handler for the web logging: Handlers.AddAdminHandler(`/log`, Web.HandlerWebLog) + + // Handler for the web logging's CSS and JS: Handlers.AddAdminHandler(`/log/css/normalize.css`, Web.HandlerCSSNormalize) Handlers.AddAdminHandler(`/log/css/webflow.css`, Web.HandlerCSSWebflow) Handlers.AddAdminHandler(`/log/css/log.css`, Web.HandlerCSSLog) @@ -41,6 +70,7 @@ func InitHandlers() { Handlers.AddAdminHandler(`/log/js/jquery.min.map`, Web.HandlerJSjQueryMap) Handlers.AddAdminHandler(`/log/js/webflow.js`, Web.HandlerJSWebflow) + // Are the static files mapped to the public? if ConfigurationDB.Read(`MapStaticFiles2Root`) == "true" { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `The static files are mapped to the root.`) Handlers.AddPublicHandler(`/`, StaticFiles.HandlerMapStaticFiles2Root) diff --git a/System/RegisterLoggingDevices.go b/System/RegisterLoggingDevices.go index d9becc3..da6eefb 100644 --- a/System/RegisterLoggingDevices.go +++ b/System/RegisterLoggingDevices.go @@ -8,11 +8,13 @@ import ( LM "github.com/SommerEngineering/Ocean/Log/Meta" ) +// Init the logging devices. func initLoggingDevices() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Init the logging devices.`) defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Init the logging devices done.`) + // Is the database logger enabled? if ConfigurationDB.Read(`LogUseDatabaseLogging`) == `true` { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `The database logger is active.`) activateDatabaseLogger() @@ -20,6 +22,7 @@ func initLoggingDevices() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `The database logger is NOT active.`) } + // Is the console logger enabled? if ConfigurationDB.Read(`LogUseConsoleLogging`) == `true` { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `The console logger is active.`) activateConsoleLogger() diff --git a/System/Start.go b/System/Start.go index 83737b8..895a824 100644 --- a/System/Start.go +++ b/System/Start.go @@ -5,8 +5,13 @@ import ( "time" ) +// The main function for the application. func StartAndBlockForever() { + + // Starts the public and private web server with own threads: WebServer.Start() + + // Wait forever: for { time.Sleep(1 * time.Second) } diff --git a/System/Variables.go b/System/Variables.go index f300352..6b878ca 100644 --- a/System/Variables.go +++ b/System/Variables.go @@ -5,5 +5,5 @@ import ( ) var ( - senderName LM.Sender = `System::System` + senderName LM.Sender = `System::System` // This is the name for logging event from this package ) diff --git a/Templates/Init.go b/Templates/Init.go index 25957bc..2512925 100644 --- a/Templates/Init.go +++ b/Templates/Init.go @@ -12,17 +12,21 @@ import ( "io/ioutil" ) +// Init the template package. func init() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Starting the template engine.`) defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Starting the template engine done.`) + // We read the templates out of the grid file system: dbSession, gridFS := CustomerDB.GridFS() defer dbSession.Close() + // Read the current version of the ZIP file: if gridFile, errGridFile := gridFS.Open(`templates.zip`); errGridFile != nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to open the templates out of the GridFS!`, errGridFile.Error()) return } else { + // Read all data in the memory cache: defer gridFile.Close() Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `Read the templates.zip file from the grid file system.`, `Upload time UTC: `+Tools.FormatTime(gridFile.UploadDate().UTC())) if data, ioError := ioutil.ReadAll(gridFile); ioError != nil { @@ -33,18 +37,24 @@ func init() { } } - // Read the content from the ZIP file: + // Opens the ZIP file from memory: reader, readerError := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData))) if readerError != nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameREAD, `Was not able to read the ZIP file.`, readerError.Error()) return } + // The in-memory container for all templates: templates = template.New(`root`) + + // Loop over all files inside the ZIP file: for _, file := range reader.File { + // Opens a file: fileReader, openError := file.Open() if openError == nil { + + // Read all bytes: contentData, readError := ioutil.ReadAll(fileReader) fileReader.Close() @@ -53,7 +63,10 @@ func init() { continue } + // Converts the bytes to string: templateData := string(contentData) + + // Try to parse the string as template: if _, err := templates.Parse(templateData); err != nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityMiddle, LM.ImpactMiddle, LM.MessageNamePARSE, fmt.Sprintf(`The template '%s' cannot be parsed.`, file.FileInfo().Name()), err.Error()) } else { diff --git a/Templates/Process.go b/Templates/Process.go index 53b7b4c..a76face 100644 --- a/Templates/Process.go +++ b/Templates/Process.go @@ -7,14 +7,19 @@ import ( "net/http" ) +// Executes a HTML template by name. func ProcessHTML(templateName string, response http.ResponseWriter, data interface{}) { + // Case: A request before the init event is done. if !isInit { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameINIT, `The template engine is not (yet) init.`) return } + // Send first the MIME type: MimeTypes.Write2HTTP(response, MimeTypes.TypeWebHTML) + + // Execute the template: if executeError := templates.ExecuteTemplate(response, templateName, data); executeError != nil { Log.LogFull(senderName, LM.CategoryAPP, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameEXECUTE, `Was not able to execute the template.`, templateName, executeError.Error()) } diff --git a/Templates/Variables.go b/Templates/Variables.go index 45e19ea..42934c6 100644 --- a/Templates/Variables.go +++ b/Templates/Variables.go @@ -6,8 +6,8 @@ import ( ) var ( - templates *template.Template = nil - senderName LM.Sender = `System::Templates` - zipData []byte = nil - isInit bool = false + templates *template.Template = nil // The in-memory cache for the templates + senderName LM.Sender = `System::Templates` // This is the name for logging event from this package + zipData []byte = nil // The in-memory cache of the ZIP file + isInit bool = false // State, if the init event is done ) diff --git a/Tools/FormatTime.go b/Tools/FormatTime.go index 7af22e4..0963eac 100644 --- a/Tools/FormatTime.go +++ b/Tools/FormatTime.go @@ -6,6 +6,7 @@ import ( "time" ) +// Formats the given time as YYYYMMdd HHmmss.fff func FormatTime(t1 time.Time) (result string) { var year int = t1.Year() var month int = int(t1.Month()) diff --git a/Tools/Hostname.go b/Tools/Hostname.go index b3a1db2..1a90a78 100644 --- a/Tools/Hostname.go +++ b/Tools/Hostname.go @@ -1,5 +1,6 @@ package Tools +// Provide the server's hostname. func ThisHostname() (result string) { result = hostname return diff --git a/Tools/IPAddresses.go b/Tools/IPAddresses.go index 65e4e3d..d203e72 100644 --- a/Tools/IPAddresses.go +++ b/Tools/IPAddresses.go @@ -5,14 +5,19 @@ import ( "strings" ) +// Provides all IP addresses for this server. func ReadAllIPAddresses4ThisHost() (addresses4Host []string) { addresses4Host = ipAddresses return } +// Read all IP addreses once. func initIPAddresses4ThisHost() { + + // Get any IP addresses: addresses, err := net.InterfaceAddrs() if err != nil { + // Case: Error! Use localhost. ipAddresses = make([]string, 1) ipAddresses[0] = `127.0.0.1` return @@ -20,13 +25,21 @@ func initIPAddresses4ThisHost() { counter := 0 ipAddresses = make([]string, len(addresses)) + + // Loop over all addresses: for _, address := range addresses { addressText := address.String() + + // Case: CIDR notation? if strings.Contains(addressText, `/`) { + // Convert the address: addressText = addressText[:strings.Index(addressText, `/`)] } + // Parse the address to determine some meta data: ip := net.ParseIP(addressText) + + // Filter out any loopback and local entries: if !ip.IsLoopback() && !ip.IsUnspecified() && strings.ToLower(ip.String()) != `fe80::1` { ipAddresses[counter] = ip.String() counter++ @@ -34,6 +47,8 @@ func initIPAddresses4ThisHost() { } if counter == 0 { + // Case: No public facing address found. + // Use localhost instead. ipAddresses = make([]string, 1) ipAddresses[0] = `127.0.0.1` } else { diff --git a/Tools/Init.go b/Tools/Init.go index 069208e..2964fee 100644 --- a/Tools/Init.go +++ b/Tools/Init.go @@ -12,7 +12,7 @@ import ( ) func init() { - // Get this hostname: + // Get the hostname of this server: if hostText, errHost := os.Hostname(); errHost != nil { panic(`Was not able to read the hostname: ` + errHost.Error()) } else { @@ -40,6 +40,6 @@ func init() { localIPAddressAndPort = fmt.Sprintf("%s:%s", allHostsIPAddresses[0], port) } - // Read the default language: + // Read the default language for I18N: defaultLanguage = ConfigurationDB.Read(`DefaultLanguageCode`) } diff --git a/Tools/InternalCommPassword.go b/Tools/InternalCommPassword.go index 4322361..bb443b6 100644 --- a/Tools/InternalCommPassword.go +++ b/Tools/InternalCommPassword.go @@ -1,5 +1,6 @@ package Tools +// Provides the communication password. func InternalCommPassword() (pwd string) { pwd = internalCommPassword return diff --git a/Tools/LocalIPAddressAndPort.go b/Tools/LocalIPAddressAndPort.go index b5d0067..64efef9 100644 --- a/Tools/LocalIPAddressAndPort.go +++ b/Tools/LocalIPAddressAndPort.go @@ -1,5 +1,6 @@ package Tools +// Returns the own (thus: local) public facing IP address with port. func LocalIPAddressAndPort() (address string) { address = localIPAddressAndPort return diff --git a/Tools/Random.go b/Tools/Random.go index 17b04b2..827d5a8 100644 --- a/Tools/Random.go +++ b/Tools/Random.go @@ -5,11 +5,13 @@ import ( "math/rand" ) +// Gets a random integer between 0 and max-1. func RandomInteger(max int) (rnd int) { rnd = rand.Intn(max) return } +// Gets a random UUID (v4). func RandomGUID() (guidString string) { guidString = uuid.NewV4().String() guidString = guidString[1 : len(guidString)-1] diff --git a/Tools/RequestLanguage.go b/Tools/RequestLanguage.go index 396621e..ea9554e 100644 --- a/Tools/RequestLanguage.go +++ b/Tools/RequestLanguage.go @@ -7,44 +7,63 @@ import ( "strings" ) +// Gets the language of a request for enable I18N. func GetRequestLanguage(request *http.Request) (resultLangs Languages) { + + // Read the goven data from the request: languageCodeForm := request.FormValue(`lang`) languageCodeHeader := request.Header.Get(`Accept-Language`) + // Is only one language available? if languageCodeForm != `` && ((len(languageCodeForm) == 2) || (len(languageCodeForm) == 5 && strings.Contains(languageCodeForm, `-`))) { + + // Case: Only one language available. resultLangs = make(Languages, 1) resultLangs[0].Factor = 1.0 resultLangs[0].Language = strings.ToLower(languageCodeForm) return } + // Case: The Accept-Language fild is empty. if languageCodeHeader == `` { + // Use the default language: resultLangs = make(Languages, 1) resultLangs[0].Factor = 1.0 resultLangs[0].Language = defaultLanguage return } + // Separate each language: values := strings.Split(languageCodeHeader, `,`) langs := make(Languages, len(values)) + + // Loop over all languages: for n, langData := range values { + + // Is the factor given? if factorData := strings.Split(langData, `;q=`); len(factorData) == 2 { + + // Try to parse the factor: if factor, errFactor := strconv.ParseFloat(factorData[1], 32); errFactor != nil { + // Case: Parsing was not possible. Use 1 instead: langs[n] = Language{} langs[n].Language = strings.ToLower(strings.Trim(factorData[0], ` `)) langs[n].Factor = 1.0 } else { + // Case: Factor was parsed. langs[n] = Language{} langs[n].Language = strings.ToLower(strings.Trim(factorData[0], ` `)) langs[n].Factor = float32(factor) } } else { + // Case: No factor given. Use 1 instead. langs[n] = Language{} langs[n].Language = strings.ToLower(strings.Trim(langData, ` `)) langs[n].Factor = 1.0 } } + // Sort all languages by factor: sort.Sort(langs) resultLangs = langs return diff --git a/Tools/ResponseLanguage.go b/Tools/ResponseLanguage.go index efadfe0..5c424ae 100644 --- a/Tools/ResponseLanguage.go +++ b/Tools/ResponseLanguage.go @@ -4,6 +4,7 @@ import ( "net/http" ) +// Send the chosen language for the content. func SendChosenLanguage(response http.ResponseWriter, lang Language) { response.Header().Add(`Content-Language`, lang.Language) } diff --git a/Tools/SchemeLanguage.go b/Tools/SchemeLanguage.go index 3e247c2..417ea42 100644 --- a/Tools/SchemeLanguage.go +++ b/Tools/SchemeLanguage.go @@ -1,20 +1,25 @@ package Tools +// The data type for the language. type Language struct { Language string Factor float32 } +// The data type for multiple languages. type Languages []Language +// Ensure, that the languages are sortable. func (lang Languages) Len() int { return len(lang) } +// Ensure, that the languages are sortable. func (lang Languages) Less(i, j int) bool { return lang[i].Factor > lang[j].Factor } +// Ensure, that the languages are sortable. func (lang Languages) Swap(i, j int) { lang[i], lang[j] = lang[j], lang[i] } diff --git a/Tools/Variables.go b/Tools/Variables.go index a5a411b..71aceec 100644 --- a/Tools/Variables.go +++ b/Tools/Variables.go @@ -5,10 +5,10 @@ import ( ) var ( - senderName LM.Sender = `System::Tools` - hostname string = `unknown` - localIPAddressAndPort string = `127.0.0.1:60000` - ipAddresses []string = nil - internalCommPassword string = `` - defaultLanguage string = `en` + senderName LM.Sender = `System::Tools` // This is the name for logging event from this package + hostname string = `unknown` // The local server's hostname + localIPAddressAndPort string = `127.0.0.1:60000` // The local server's IP address and port + ipAddresses []string = nil // All public IP addresses of this server + internalCommPassword string = `` // The communication password + defaultLanguage string = `en` // The default language for I18N ) diff --git a/Variables.go b/Variables.go index 678e9ae..1f56f2c 100644 --- a/Variables.go +++ b/Variables.go @@ -5,5 +5,5 @@ import ( ) var ( - senderName LM.Sender = `Ocean` + senderName LM.Sender = `Ocean` // This is the name for logging event from this package ) diff --git a/WebContent/GetContent.go b/WebContent/GetContent.go index c071e9c..7fa3af0 100644 --- a/WebContent/GetContent.go +++ b/WebContent/GetContent.go @@ -9,8 +9,10 @@ import ( "io/ioutil" ) +// Get the file bytes for any web content file. func GetContent(path string) (content []byte, err error) { + // Open the ZIP file reader with the data out of the memory: reader, readerError := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData))) if readerError != nil { err = errors.New("Was not able to read the ZIP file: " + readerError.Error()) @@ -18,13 +20,18 @@ func GetContent(path string) (content []byte, err error) { return } + // Iterate over all files within the ZIP: for _, file := range reader.File { + + // Is this the desired file? if file.Name == path { + // Open the file: fileReader, openError := file.Open() defer fileReader.Close() - if openError == nil { + + // Read all bytes: contentData, readError := ioutil.ReadAll(fileReader) if readError != nil { @@ -33,6 +40,7 @@ func GetContent(path string) (content []byte, err error) { return } + // Return the data: content = contentData return } diff --git a/WebContent/Handler.go b/WebContent/Handler.go index 9cc8cf8..fe16642 100644 --- a/WebContent/Handler.go +++ b/WebContent/Handler.go @@ -8,12 +8,16 @@ import ( "strings" ) +// The handler to deliver web framework files e.g. jQuery, Bootstrap, etc. func HandlerDeliverFramework(response http.ResponseWriter, request *http.Request) { + + // If the system is going down, send an error: if Shutdown.IsDown() { http.NotFound(response, request) return } + // Replace the prefix: path := strings.Replace(request.URL.Path, "/framework/", "", 1) sendError := SendContent(response, path) diff --git a/WebContent/Init.go b/WebContent/Init.go index 598dce0..90c1c82 100644 --- a/WebContent/Init.go +++ b/WebContent/Init.go @@ -8,24 +8,32 @@ import ( "io/ioutil" ) +/* +Init the web content package. It is used to provided some static data for web +frameworks like e.g. jQuery, d3.js, Bootstrap, etc. +*/ func init() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Run init for the web content.`) defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Init for web content done.`) + // Ensure that we init this package only once: if isInit { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityHigh, LM.ImpactNone, LM.MessageNameINIT, `The web content is already fine.`) return } + // Get the filename out of the database: filename = ConfigurationDB.Read(`FilenameWebResources`) dbSession, gridFS := CustomerDB.GridFS() defer dbSession.Close() + // Open the file out of the grid file system: if gridFile, errGridFile := gridFS.Open(filename); errGridFile != nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to open the web content out of the GridFS!`, filename, errGridFile.Error()) return } else { + // Read all the data in the memeory cache: defer gridFile.Close() if data, ioError := ioutil.ReadAll(gridFile); ioError != nil { Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to read the web content file.`, filename, ioError.Error()) diff --git a/WebContent/Send.go b/WebContent/Send.go index 3b025df..c762067 100644 --- a/WebContent/Send.go +++ b/WebContent/Send.go @@ -10,8 +10,10 @@ import ( "net/http" ) +// Send any web content file to the client. func SendContent(response http.ResponseWriter, path string) (err error) { + // Read the bytes: content, contError := GetContent(path) if contError != nil { err = errors.New("Was not able to read the needed content: " + contError.Error()) @@ -19,6 +21,7 @@ func SendContent(response http.ResponseWriter, path string) (err error) { return } + // Determine some meta data: contentLength := len(content) contentType, typeError := MimeTypes.DetectType(path) @@ -28,10 +31,12 @@ func SendContent(response http.ResponseWriter, path string) (err error) { return } + // Send the meta data to the client: response.Header().Add("Content-Length", fmt.Sprintf("%d", contentLength)) response.Header().Add("Content-Type", contentType.MimeType) response.WriteHeader(http.StatusOK) + // Write the binary data to the client: buffer := bytes.NewBuffer(content) _, writeError := buffer.WriteTo(response) diff --git a/WebContent/Variables.go b/WebContent/Variables.go index 905a02b..cd4b603 100644 --- a/WebContent/Variables.go +++ b/WebContent/Variables.go @@ -5,8 +5,8 @@ import ( ) var ( - isInit = false - filename = "" - zipData []byte = nil - senderName LM.Sender = `System::WebContent` + isInit = false // Ensure that the init happens only once + filename = "" // The filename of web content file at the grid file system + zipData []byte = nil // The memory cache of the data + senderName LM.Sender = `System::WebContent` // This is the name for logging event from this package ) diff --git a/WebServer/Init.go b/WebServer/Init.go index cf7fd41..78da85c 100644 --- a/WebServer/Init.go +++ b/WebServer/Init.go @@ -13,14 +13,19 @@ import ( "time" ) +// Setup the web server. func init() { Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Init the web server now.`) defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Done init the web server.`) + // Public web server: use the servers public facing IP address and the configured port: serverPublicAddressPort = Tools.LocalIPAddressAndPort() + + // Private web server: use the configured end point: serverAdminAddressPort = ConfigurationDB.Read(`AdminWebServerBinding`) + // Setup the public web server: serverPublic = &http.Server{} serverPublic.Addr = serverPublicAddressPort serverPublic.Handler = Handlers.GetPublicMux() @@ -28,6 +33,8 @@ func init() { // Public Web Server: Read Timeout if readTimeoutSeconds, errTimeout := strconv.Atoi(ConfigurationDB.Read(`PublicWebServerReadTimeoutSeconds`)); errTimeout != nil { + + // Case: Error! Use a default timeout: Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityLow, LM.ImpactLow, LM.MessageNameCONFIGURATION, `Was not able to read the public server's read timeout value. Use the default of 10 seconds instead.`, errTimeout.Error()) serverPublic.ReadTimeout = 10 * time.Second } else { @@ -37,6 +44,8 @@ func init() { // Public Web Server: Write Timeout if writeTimeoutSeconds, errTimeout := strconv.Atoi(ConfigurationDB.Read(`PublicWebServerWriteTimeoutSeconds`)); errTimeout != nil { + + // Case: Error! Use a default timeout: Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityLow, LM.ImpactLow, LM.MessageNameCONFIGURATION, `Was not able to read the public server's write timeout value. Use the default of 10 seconds instead.`, errTimeout.Error()) serverPublic.WriteTimeout = 10 * time.Second } else { @@ -46,6 +55,8 @@ func init() { // Public Web Server: Max. Header Size if maxHeaderBytes, errHeaderBytes := strconv.Atoi(ConfigurationDB.Read(`PublicWebServerMaxHeaderLenBytes`)); errHeaderBytes != nil { + + // Case: Error! Use a default threshold for the header size: Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityLow, LM.ImpactLow, LM.MessageNameCONFIGURATION, `Was not able to read the public server's max. header size. Use the default of 1048576 bytes instead.`, errHeaderBytes.Error()) serverPublic.MaxHeaderBytes = 1048576 } else { @@ -53,7 +64,10 @@ func init() { serverPublic.MaxHeaderBytes = maxHeaderBytes } + // Is the private web server (i.e. administration server) enabled? if strings.ToLower(ConfigurationDB.Read(`AdminWebServerEnabled`)) == `true` { + + // Setup the private web server: serverAdmin = &http.Server{} serverAdmin.Addr = serverAdminAddressPort serverAdmin.Handler = Handlers.GetAdminMux() @@ -61,6 +75,8 @@ func init() { // Admin Web Server: Read Timeout if readTimeoutSeconds, errTimeout := strconv.Atoi(ConfigurationDB.Read(`AdminWebServerReadTimeoutSeconds`)); errTimeout != nil { + + // Case: Error! Use a default timeout: Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityLow, LM.ImpactLow, LM.MessageNameCONFIGURATION, `Was not able to read the admin server's read timeout value. Use the default of 10 seconds instead.`, errTimeout.Error()) serverAdmin.ReadTimeout = 10 * time.Second } else { @@ -70,6 +86,8 @@ func init() { // Admin Web Server: Write Timeout if writeTimeoutSeconds, errTimeout := strconv.Atoi(ConfigurationDB.Read(`AdminWebServerWriteTimeoutSeconds`)); errTimeout != nil { + + // Case: Error! Use a default timeout: Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityLow, LM.ImpactLow, LM.MessageNameCONFIGURATION, `Was not able to read the admin server's write timeout value. Use the default of 10 seconds instead.`, errTimeout.Error()) serverAdmin.WriteTimeout = 10 * time.Second } else { @@ -79,6 +97,8 @@ func init() { // Admin Web Server: Max. Header Size if maxHeaderBytes, errHeaderBytes := strconv.Atoi(ConfigurationDB.Read(`AdminWebServerMaxHeaderLenBytes`)); errHeaderBytes != nil { + + // Case: Error! Use a default threshold for the header size: Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityLow, LM.ImpactLow, LM.MessageNameCONFIGURATION, `Was not able to read the admin server's max. header size. Use the default of 1048576 bytes instead.`, errHeaderBytes.Error()) serverAdmin.MaxHeaderBytes = 1048576 } else { @@ -86,7 +106,7 @@ func init() { serverAdmin.MaxHeaderBytes = maxHeaderBytes } } else { - // Admin Web Server is disabled + // Private web server is disabled: Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `The admin web server is disabled.`) } } diff --git a/WebServer/Start.go b/WebServer/Start.go index 8559c61..374ef9c 100644 --- a/WebServer/Start.go +++ b/WebServer/Start.go @@ -12,17 +12,20 @@ func Start() { // Tell the whole cluster, that we are up and ready: data := SystemMessages.ICCCStartUpMessage{} + // Start the public web server: if serverPublic != nil { data.PublicIPAddressAndPort = serverPublicAddressPort Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Public web server is now listening.`, `Configuration for hostname and port.`, serverPublicAddressPort) go serverPublic.ListenAndServe() } + // Start the private web server: if serverAdmin != nil { data.AdminIPAddressAndPort = serverAdminAddressPort Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Admin web server is now listening.`, `Configuration for hostname and port.`, serverAdminAddressPort) go serverAdmin.ListenAndServe() } + // Notify the whole cluster, that this server is now up and ready: ICCC.WriteMessage2All(ICCC.ChannelSYSTEM, `System::Start`, data) } diff --git a/WebServer/Variables.go b/WebServer/Variables.go index e195fb3..5ff2e10 100644 --- a/WebServer/Variables.go +++ b/WebServer/Variables.go @@ -6,9 +6,9 @@ import ( ) var ( - senderName LM.Sender = `System::WebServer` - serverPublic *http.Server = nil - serverAdmin *http.Server = nil - serverPublicAddressPort string = "" - serverAdminAddressPort string = "" + senderName LM.Sender = `System::WebServer` // This is the name for logging event from this package + serverPublic *http.Server = nil // The public web server + serverAdmin *http.Server = nil // The private web server + serverPublicAddressPort string = "" // The public server end-point + serverAdminAddressPort string = "" // The private server end-point )