github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/main.go (about)

     1  // Copyright 2023 IAC. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package main
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"os"
    22  	"os/signal"
    23  	"sync"
    24  	"syscall"
    25  	"time"
    26  
    27  	"log"
    28  	"net/http"
    29  	"net/http/httputil"
    30  	"net/url"
    31  
    32  	"plugin"
    33  	"reflect"
    34  
    35  	"github.com/gin-gonic/gin"
    36  	"github.com/google/uuid"
    37  	configuration "github.com/mdaxf/iac/config"
    38  	dbconn "github.com/mdaxf/iac/databases"
    39  	mongodb "github.com/mdaxf/iac/documents"
    40  
    41  	"github.com/mdaxf/iac/com"
    42  )
    43  
    44  var wg sync.WaitGroup
    45  var router *gin.Engine
    46  
    47  // main is the entry point of the program.
    48  // It loads the configuration file, initializes the database connection, and starts the server.
    49  // It also loads controllers dynamically and statically based on the configuration file.
    50  // The server is started on the port specified in the configuration file.
    51  // The server serves static files from the portal directory.
    52  // The server also serves static files from the plugins directory.
    53  
    54  func main() {
    55  
    56  	Initialized = false
    57  	startTime := time.Now()
    58  	defer func() {
    59  		elapsed := time.Since(startTime)
    60  		if Initialized {
    61  			ilog.PerformanceWithDuration("main", elapsed)
    62  		}
    63  	}()
    64  	/*
    65  		defer func() {
    66  			if r := recover(); r != nil {
    67  				if Initialized {
    68  					ilog.Error(fmt.Sprintf("Panic: %v", r))
    69  				} else {
    70  					log.Fatalf("Panic: %v", r)
    71  				}
    72  			}
    73  		}()  */
    74  	// Load configuration from the file
    75  
    76  	config, err := configuration.LoadConfig()
    77  	if err != nil {
    78  		log.Fatalf("Failed to load configuration: %v", err)
    79  		//	ilog.Error("Failed to load configuration: %v", err)
    80  	}
    81  
    82  	configuration.GlobalConfiguration, err = configuration.LoadGlobalConfig()
    83  
    84  	if err != nil {
    85  		log.Fatalf("Failed to load global configuration: %v", err)
    86  		//	ilog.Error("Failed to load global configuration: %v", err)
    87  	}
    88  	com.NodeHeartBeats = make(map[string]interface{})
    89  
    90  	com.IACNode = make(map[string]interface{})
    91  	appid := uuid.New().String()
    92  	com.IACNode["Name"] = "iac"
    93  	com.IACNode["AppID"] = appid
    94  	com.IACNode["Type"] = "Application Server"
    95  	com.IACNode["Version"] = "1.0.0"
    96  	com.IACNode["Description"] = "IAC Application Server"
    97  	com.IACNode["Status"] = "Started"
    98  	com.IACNode["StartTime"] = time.Now()
    99  	data := make(map[string]interface{})
   100  	data["Node"] = com.IACNode
   101  	data["Result"] = make(map[string]interface{})
   102  
   103  	com.NodeHeartBeats[appid] = data
   104  
   105  	initialize()
   106  
   107  	if dbconn.DB != nil {
   108  		defer dbconn.DB.Close()
   109  	} else {
   110  		//log.Fatalf("Failed to connect to database")
   111  		ilog.Error("Failed to connect to database")
   112  	}
   113  
   114  	if mongodb.DocDBCon.MongoDBClient != nil {
   115  		defer mongodb.DocDBCon.MongoDBClient.Disconnect(context.Background())
   116  	} else {
   117  		//log.Fatalf("Failed to connect to database")
   118  		ilog.Error("Failed to connect to document database")
   119  	}
   120  	// Initialize the Gin router
   121  
   122  	for _, dbclient := range com.MongoDBClients {
   123  		if dbclient != nil {
   124  			defer dbclient.Disconnect(context.Background())
   125  		} else {
   126  			//log.Fatalf("Failed to connect to database")
   127  			ilog.Error("Failed to connect to the configured document database")
   128  		}
   129  
   130  	}
   131  
   132  	if com.IACMessageBusClient != nil {
   133  		defer com.IACMessageBusClient.Stop()
   134  	} else {
   135  		//log.Fatalf("Failed to connect to database")
   136  		ilog.Error("Failed to connect to the configured message bus")
   137  	}
   138  	portal := config.Portal
   139  
   140  	router = gin.Default()
   141  	// Load controllers dynamically based on the configuration file
   142  	plugincontrollers := make(map[string]interface{})
   143  	for _, controllerConfig := range config.PluginControllers {
   144  
   145  		jsonString, err := json.Marshal(controllerConfig)
   146  		if err != nil {
   147  
   148  			ilog.Error(fmt.Sprintf("Error marshaling json: %v", err))
   149  			return
   150  		}
   151  		fmt.Println(string(jsonString))
   152  		controllerModule, err := loadpluginControllerModule(controllerConfig.Path)
   153  		if err != nil {
   154  			ilog.Error(fmt.Sprintf("Failed to load controller module %s: %v", controllerConfig.Path, err))
   155  		}
   156  		plugincontrollers[controllerConfig.Path] = controllerModule
   157  	}
   158  
   159  	go func() {
   160  		// Create endpoints dynamically based on the configuration file
   161  		for _, controllerConfig := range config.PluginControllers {
   162  			for _, endpointConfig := range controllerConfig.Endpoints {
   163  				method := endpointConfig.Method
   164  				path := fmt.Sprintf("/%s%s", controllerConfig.Path, endpointConfig.Path)
   165  				handler := plugincontrollers[controllerConfig.Path].(map[string]interface{})[endpointConfig.Handler].(func(*gin.Context))
   166  				router.Handle(method, path, handler)
   167  			}
   168  		}
   169  	}()
   170  	// Load controllers statically based on the configuration file
   171  	ilog.Info("Loading controllers")
   172  
   173  	go func() {
   174  		loadControllers(router, config.Controllers)
   175  	}()
   176  	// Start the portals
   177  	ilog.Info("Starting portals")
   178  
   179  	jsonString, err := json.Marshal(config.Portal)
   180  	if err != nil {
   181  		ilog.Error(fmt.Sprintf("Error marshaling json: %v", err))
   182  		return
   183  	}
   184  	fmt.Println(string(jsonString))
   185  
   186  	ilog.Info(fmt.Sprintf("Starting portal on port %d, page:%s, logon: %s", portal.Port, portal.Home, portal.Logon))
   187  
   188  	clientconfig := make(map[string]interface{})
   189  	clientconfig["signalrconfig"] = com.SingalRConfig
   190  	clientconfig["instance"] = com.Instance
   191  	clientconfig["instanceType"] = com.InstanceType
   192  	clientconfig["instanceName"] = com.InstanceName
   193  
   194  	router.GET("/app/config", func(c *gin.Context) {
   195  		c.JSON(http.StatusOK, clientconfig)
   196  	})
   197  
   198  	router.GET("/app/debug", func(c *gin.Context) {
   199  		headers := c.Request.Header
   200  		useragent := c.Request.Header.Get("User-Agent")
   201  		ilog.Debug(fmt.Sprintf("User-Agent: %s, headers: %v", useragent, headers))
   202  		debugInfo := map[string]interface{}{
   203  			"Route":          c.FullPath(),
   204  			"requestheader":  headers,
   205  			"User-Agent":     useragent,
   206  			"requestbody":    c.Request.Body,
   207  			"responseheader": c.Writer.Header(),
   208  			"Method":         c.Request.Method,
   209  		}
   210  
   211  		c.JSON(http.StatusOK, debugInfo)
   212  	})
   213  	/*
   214  		router.Use(static.Serve("/portal", static.LocalFile("./portal", true)))
   215  		router.Use(static.Serve("/portal/scripts", static.LocalFile("./portal/scripts", true)))*/
   216  	/*
   217  
   218  	 */
   219  	// Start the server
   220  	//go router.Run(fmt.Sprintf(":%d", config.Port))
   221  
   222  	server := &http.Server{
   223  		Addr:         fmt.Sprintf(":%d", config.Port), // Set your desired port
   224  		Handler:      router,
   225  		ReadTimeout:  time.Duration(config.Timeout) * time.Millisecond,   // Set read timeout
   226  		WriteTimeout: time.Duration(2*config.Timeout) * time.Millisecond, // Set write timeout
   227  		IdleTimeout:  time.Duration(3*config.Timeout) * time.Millisecond, // Set idle timeout
   228  	}
   229  
   230  	go func() {
   231  		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
   232  			ilog.Error(fmt.Sprintf("Failed to start server: %v", err))
   233  			panic(err)
   234  		}
   235  	}()
   236  
   237  	ilog.Info(fmt.Sprintf("Started iac endpoint server on port %d, page:%s, logon: %s", portal.Port, portal.Home, portal.Logon))
   238  	waitForTerminationSignal()
   239  	elapsed := time.Since(startTime)
   240  	ilog.PerformanceWithDuration("main.main", elapsed)
   241  
   242  	wg.Wait()
   243  
   244  }
   245  
   246  // loadpluginControllerModule loads a plugin controller module from the specified controllerPath.
   247  // It returns the loaded module as an interface{} and an error if any.
   248  // The module is loaded from the plugins directory.
   249  func loadpluginControllerModule(controllerPath string) (interface{}, error) {
   250  
   251  	ilog.Info(fmt.Sprintf("Loading plugin controllers %s", controllerPath))
   252  
   253  	modulePath := fmt.Sprintf("./plugins/%s/%s.so", controllerPath, controllerPath)
   254  	print(modulePath)
   255  	module, err := plugin.Open(modulePath)
   256  	if err != nil {
   257  
   258  		return nil, fmt.Errorf("Failed to open controller module %s: %v", modulePath, err)
   259  	}
   260  	sym, err := module.Lookup(controllerPath + "Controller")
   261  	if err != nil {
   262  		return nil, fmt.Errorf("Failed to lookup symbol in controller module %s: %v", modulePath, err)
   263  	}
   264  	return sym, nil
   265  }
   266  
   267  // getpluginHandlerFunc is a function that returns a gin.HandlerFunc for loading and executing a plugin handler function.
   268  // It takes a module reflect.Value and the name of the handler function as parameters.
   269  // The function loads the plugin handler function from the module using reflection and returns a gin.HandlerFunc.
   270  // The returned handler function takes a *gin.Context as a parameter and executes the plugin handler function with the context.
   271  // If the plugin handler function returns an error, the handler function aborts the request with a 500 Internal Server Error.
   272  // If the plugin handler function returns a []byte, the handler function sends the data as a JSON response with a 200 OK status code.
   273  // If the plugin handler function does not return an error or []byte, the handler function sends a 200 OK status code.
   274  // If the plugin handler function does not exist, the function returns nil.
   275  func getpluginHandlerFunc(module reflect.Value, name string) gin.HandlerFunc {
   276  
   277  	ilog.Info(fmt.Sprintf("Loading plugin handler function: %s", name))
   278  
   279  	method := module.MethodByName(name)
   280  	if !method.IsValid() {
   281  		return nil
   282  	}
   283  
   284  	return func(c *gin.Context) {
   285  		in := make([]reflect.Value, 1)
   286  		in[0] = reflect.ValueOf(c)
   287  		out := method.Call(in)
   288  		if len(out) > 0 {
   289  			if err, ok := out[0].Interface().(error); ok {
   290  				c.AbortWithError(http.StatusInternalServerError, err)
   291  				return
   292  			}
   293  			if data, ok := out[0].Interface().([]byte); ok {
   294  				c.Data(http.StatusOK, "application/json", data)
   295  				return
   296  			}
   297  		}
   298  		c.Status(http.StatusOK)
   299  	}
   300  }
   301  
   302  // CORSMiddleware is a middleware function that adds Cross-Origin Resource Sharing (CORS) headers to the HTTP response.
   303  // It allows requests from a specified origin and supports various HTTP methods.
   304  // The allowOrigin parameter specifies the allowed origin for CORS requests.
   305  // This middleware function also handles preflight requests by responding with appropriate headers.
   306  
   307  func CORSMiddleware(allowOrigin string) gin.HandlerFunc {
   308  	return func(c *gin.Context) {
   309  		c.Header("Access-Control-Allow-Origin", allowOrigin)
   310  		c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
   311  		//  c.Header("Access-Control-Allow-Headers", "Authorization, Content-Type, Origin")
   312  		c.Writer.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type, Content-Length, X-CSRF-Token, Token, session, Origin, Host, Connection, Accept-Encoding, Accept-Language, X-Requested-With")
   313  
   314  		//	ilog.Debug(fmt.Sprintf("CORSMiddleware: %s", allowOrigin))
   315  		//	ilog.Debug(fmt.Sprintf("CORSMiddleware header: %s", c.Request.Header))
   316  		if c.Request.Method == "OPTIONS" {
   317  			c.AbortWithStatus(204)
   318  			return
   319  		}
   320  
   321  		c.Next()
   322  	}
   323  }
   324  
   325  // GinMiddleware is a middleware function that sets the specified headers in the HTTP response.
   326  // It takes a map of headers as input and returns a gin.HandlerFunc.
   327  // The middleware sets the headers in the response using the values provided in the headers map.
   328  // If the HTTP request method is OPTIONS, it aborts the request with a status code of 204 (No Content).
   329  // After setting the headers, it calls the next handler in the chain.
   330  // The next handler can be a controller function or another middleware function.
   331  // The next handler can also be a gin.HandlerFunc.
   332  func GinMiddleware(headers map[string]interface{}) gin.HandlerFunc {
   333  	return func(c *gin.Context) {
   334  
   335  		//	ilog.Debug(fmt.Sprintf("GinMiddleware: %v", headers))
   336  		//	ilog.Debug(fmt.Sprintf("GinMiddleware header: %s", c.Request.Header))
   337  
   338  		for key, value := range headers {
   339  			c.Header(key, value.(string))
   340  			//	c.Writer.Header().Set(key, value.(string))
   341  		}
   342  
   343  		if c.Request.Method == http.MethodOptions {
   344  			c.AbortWithStatus(http.StatusNoContent)
   345  			return
   346  		}
   347  		c.Next()
   348  	}
   349  }
   350  
   351  // renderproxy is a function that renders a proxy configuration by creating routes in a gin.Engine instance.
   352  // It takes a map of proxy configurations and a pointer to a gin.Engine as parameters.
   353  // Each key-value pair in the proxy map represents a route path and its corresponding target URL.
   354  // The function iterates over the proxy map and creates a route for each key-value pair in the gin.Engine instance.
   355  // When a request matches a route, the function sets up a reverse proxy to forward the request to the target URL.
   356  // The function also updates the request URL path based on the "path" parameter in the route.
   357  // Note that the ServeHTTP method of the reverse proxy is non-blocking and uses a goroutine under the hood.
   358  
   359  func renderproxy(proxy map[string]interface{}, router *gin.Engine) {
   360  	ilog.Debug(fmt.Sprintf("renderproxy: %v", proxy))
   361  
   362  	for key, value := range proxy {
   363  		ilog.Debug(fmt.Sprintf("renderproxy key: %s, value: %s", key, value))
   364  
   365  		nextURL, _ := url.Parse((value).(string))
   366  		ilog.Debug(fmt.Sprintf("renderproxy nextURL: %v", nextURL))
   367  
   368  		router.Any(fmt.Sprintf("/%s/*path", key), func(c *gin.Context) {
   369  
   370  			ilog.Debug(fmt.Sprintf("renderproxy path: %s, target: %s", c.Request.URL.Path, nextURL))
   371  
   372  			proxy := httputil.NewSingleHostReverseProxy(nextURL)
   373  
   374  			// Update the headers to allow for SSL redirection
   375  			//	req := c.Request
   376  			//	req.URL.Host = nextURL.Host
   377  			//	req.URL.Scheme = nextURL.Scheme
   378  			//req.Header["X-Forwarded-Host"] = req.Header["Host"]
   379  
   380  			c.Request.URL.Path = c.Param("path")
   381  
   382  			ilog.Debug(fmt.Sprintf("request: %v", c.Request))
   383  			// Note that ServeHttp is non blocking and uses a go routine under the hood
   384  			proxy.ServeHTTP(c.Writer, c.Request)
   385  
   386  		})
   387  
   388  	}
   389  }
   390  
   391  func waitForTerminationSignal() {
   392  	c := make(chan os.Signal, 1)
   393  	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
   394  	<-c
   395  	fmt.Println("\nShutting down...")
   396  
   397  	time.Sleep(2 * time.Second) // Add any cleanup or graceful shutdown logic here
   398  	os.Exit(0)
   399  }