github.com/vmware/transport-go@v1.3.4/plank/pkg/server/initialize.go (about)

     1  package server
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/gorilla/mux"
     6  	"github.com/prometheus/client_golang/prometheus"
     7  	"github.com/prometheus/client_golang/prometheus/promhttp"
     8  	"github.com/sirupsen/logrus"
     9  	"github.com/vmware/transport-go/bus"
    10  	"github.com/vmware/transport-go/model"
    11  	"github.com/vmware/transport-go/plank/pkg/middleware"
    12  	"github.com/vmware/transport-go/plank/utils"
    13  	"github.com/vmware/transport-go/service"
    14  	"github.com/vmware/transport-go/stompserver"
    15  	"log"
    16  	"net/http"
    17  	_ "net/http/pprof"
    18  	"path/filepath"
    19  	"reflect"
    20  	"runtime"
    21  	"time"
    22  )
    23  
    24  // initialize sets up basic configurations according to the serverConfig object such as setting output writer,
    25  // log formatter, creating a router instance, and setting up an HttpServer instance.
    26  func (ps *platformServer) initialize() {
    27  	var err error
    28  
    29  	// initialize core components
    30  	var serviceRegistryInstance = service.GetServiceRegistry()
    31  	var svcLifecycleManager = service.GetServiceLifecycleManager()
    32  
    33  	// create essential bus channels
    34  	ps.eventbus.GetChannelManager().CreateChannel(PLANK_SERVER_ONLINE_CHANNEL)
    35  
    36  	// initialize HTTP endpoint handlers map
    37  	ps.endpointHandlerMap = map[string]http.HandlerFunc{}
    38  	ps.serviceChanToBridgeEndpoints = make(map[string][]string, 0)
    39  
    40  	// initialize log output streams
    41  	if err = ps.serverConfig.LogConfig.PrepareLogFiles(); err != nil {
    42  		panic(err)
    43  	}
    44  
    45  	// alias outputLogFp as ps.out for platform log outputs
    46  	ps.out = ps.serverConfig.LogConfig.GetPlatformLogFilePointer()
    47  
    48  	// set logrus out writer options and assign output stream to ps.out
    49  	formatter := utils.CreateTextFormatterFromFormatOptions(ps.serverConfig.LogConfig.FormatOptions)
    50  	utils.Log.SetFormatter(formatter)
    51  	utils.Log.SetOutput(ps.out)
    52  
    53  	// if debug flag is provided enable extra logging. also, enable profiling at port 6060
    54  	if ps.serverConfig.Debug {
    55  		utils.Log.SetLevel(logrus.DebugLevel)
    56  		go func() {
    57  			runtime.SetBlockProfileRate(1) // capture traces of all possible contended mutex holders
    58  			profilerRouter := mux.NewRouter()
    59  			profilerRouter.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux)
    60  			if err := http.ListenAndServe(":6060", profilerRouter); err != nil {
    61  				panic(err)
    62  			}
    63  		}()
    64  		utils.Log.Debugln("Debug logging and profiling enabled. Available types of profiles at http://localhost:6060/debug/pprof")
    65  	}
    66  
    67  	// set a new route handler
    68  	ps.router = mux.NewRouter().Schemes("http", "https").Subrouter()
    69  
    70  	// register a reserved path /health for use with container orchestration layer like k8s
    71  	ps.endpointHandlerMap["/health"] = func(w http.ResponseWriter, r *http.Request) {
    72  		_, _ = w.Write([]byte("OK"))
    73  	}
    74  	ps.router.Path("/health").Name("/health").Handler(
    75  		middleware.CacheControlMiddleware([]string{"/health"}, middleware.NewCacheControlDirective().NoStore())(ps.endpointHandlerMap["/health"]))
    76  
    77  	// register a reserved path /prometheus for runtime metrics, if enabled
    78  	if ps.serverConfig.EnablePrometheus {
    79  		ps.endpointHandlerMap["/prometheus"] = middleware.BasicSecurityHeaderMiddleware()(promhttp.HandlerFor(
    80  			prometheus.DefaultGatherer,
    81  			promhttp.HandlerOpts{
    82  				EnableOpenMetrics: true,
    83  			})).(http.HandlerFunc)
    84  		ps.router.Path("/prometheus").Name("/prometheus").Methods(http.MethodGet).Handler(
    85  			ps.endpointHandlerMap["/prometheus"])
    86  	}
    87  
    88  	// register static paths
    89  	for _, dir := range ps.serverConfig.StaticDir {
    90  		p, uri := utils.DeriveStaticURIFromPath(dir)
    91  		utils.Log.Debugf("Serving static path %s at %s", p, uri)
    92  		ps.SetStaticRoute(uri, p)
    93  	}
    94  
    95  	// create an Http server instance
    96  	ps.HttpServer = &http.Server{
    97  		Addr:         fmt.Sprintf(":%d", ps.serverConfig.Port),
    98  		ReadTimeout:  60 * time.Second,
    99  		WriteTimeout: 60 * time.Second,
   100  		ErrorLog:     log.New(ps.serverConfig.LogConfig.GetErrorLogFilePointer(), "ERROR ", log.LstdFlags),
   101  	}
   102  
   103  	// set up a listener to receive REST bridge configs for services and set them up according to their specs
   104  	lcmChanHandler, err := ps.eventbus.ListenStreamForDestination(service.LifecycleManagerChannelName, ps.eventbus.GetId())
   105  	if err != nil {
   106  		utils.Log.Fatalln(err)
   107  	}
   108  
   109  	lcmChanHandler.Handle(func(message *model.Message) {
   110  		request, ok := message.Payload.(*service.SetupRESTBridgeRequest)
   111  		if !ok {
   112  			utils.Log.Errorf("failed to set up REST bridge ")
   113  		}
   114  
   115  		fabricSvc, _ := serviceRegistryInstance.GetService(request.ServiceChannel)
   116  		svcReadyStore := ps.eventbus.GetStoreManager().GetStore(service.ServiceReadyStore)
   117  		hooks := svcLifecycleManager.GetServiceHooks(request.ServiceChannel)
   118  
   119  		if request.Override {
   120  			// clear old bridges affected by this override. there's a suboptimal workaround for mux.Router not
   121  			// supporting a way to dynamically remove routers slice. see clearHttpChannelBridgesForService for details
   122  			newRouter := ps.clearHttpChannelBridgesForService(request.ServiceChannel)
   123  			ps.loadGlobalHttpHandler(newRouter)
   124  		}
   125  
   126  		for _, config := range request.Config {
   127  			ps.SetHttpChannelBridge(config)
   128  		}
   129  
   130  		// REST bridge setup done. now wait for service to be ready
   131  		if val, found := svcReadyStore.Get(request.ServiceChannel); !found || !val.(bool) {
   132  			readyChan := hooks.OnServiceReady()
   133  			svcReadyStore.Put(request.ServiceChannel, <-readyChan, service.ServiceInitStateChange)
   134  			utils.Log.Infof("[plank] Service '%s' initialized successfully", reflect.TypeOf(fabricSvc).String())
   135  			close(readyChan)
   136  		}
   137  
   138  	}, func(err error) {
   139  		utils.Log.Errorln(err)
   140  	})
   141  
   142  	// instantiate a new middleware manager
   143  	ps.middlewareManager = middleware.NewMiddlewareManager(&ps.endpointHandlerMap, ps.router)
   144  
   145  	// create an internal bus channel to notify significant changes in sessions such as disconnect
   146  	if ps.serverConfig.FabricConfig != nil {
   147  		channelManager := ps.eventbus.GetChannelManager()
   148  		channelManager.CreateChannel(bus.STOMP_SESSION_NOTIFY_CHANNEL)
   149  	}
   150  
   151  	// configure Fabric
   152  	ps.configureFabric()
   153  
   154  	// print out the quick summary of the server configuration, if NoBanner is false
   155  	if !ps.serverConfig.NoBanner {
   156  		ps.printBanner()
   157  	}
   158  }
   159  
   160  func (ps *platformServer) configureFabric() {
   161  	if ps.serverConfig.FabricConfig == nil {
   162  		return
   163  	}
   164  
   165  	var err error
   166  	if ps.serverConfig.FabricConfig.UseTCP {
   167  		ps.fabricConn, err = stompserver.NewTcpConnectionListener(fmt.Sprintf(":%d", ps.serverConfig.FabricConfig.TCPPort))
   168  	} else {
   169  		ps.fabricConn, err = stompserver.NewWebSocketConnectionFromExistingHttpServer(
   170  			ps.HttpServer,
   171  			ps.router,
   172  			ps.serverConfig.FabricConfig.FabricEndpoint,
   173  			nil) // TODO: consider tightening access by allowing configuring allowedOrigins
   174  	}
   175  
   176  	// if creation of listener fails, crash and burn
   177  	if err != nil {
   178  		panic(err)
   179  	}
   180  }
   181  
   182  func (ps *platformServer) configureSPA() {
   183  	if ps.serverConfig.SpaConfig == nil {
   184  		return
   185  	}
   186  
   187  	// TODO: error if the base uri conflicts with another URI registered before
   188  	for _, asset := range ps.serverConfig.SpaConfig.StaticAssets {
   189  		folderPath, uri := utils.DeriveStaticURIFromPath(asset)
   190  		ps.SetStaticRoute(
   191  			utils.SanitizeUrl(uri, false),
   192  			folderPath,
   193  			ps.serverConfig.SpaConfig.CacheControlMiddleware())
   194  	}
   195  
   196  	// TODO: consider handling handlers of conflicting keys
   197  	endpointHandlerMapKey := ps.serverConfig.SpaConfig.BaseUri + "*"
   198  	ps.endpointHandlerMap[endpointHandlerMapKey] = func(w http.ResponseWriter, r *http.Request) { // '*' at the end of BaseUri is to indicate it is a prefix route handler
   199  		resource := "index.html"
   200  
   201  		// if the URI contains an extension we treat it as access to static resources
   202  		if len(filepath.Ext(r.URL.Path)) > 0 {
   203  			resource = filepath.Clean(r.URL.Path)
   204  		}
   205  		http.ServeFile(w, r, filepath.Join(ps.serverConfig.SpaConfig.RootFolder, resource))
   206  	}
   207  
   208  	spaConfigCacheControlMiddleware := ps.serverConfig.SpaConfig.CacheControlMiddleware()
   209  	ps.router.
   210  		PathPrefix(ps.serverConfig.SpaConfig.BaseUri).
   211  		Name(endpointHandlerMapKey).
   212  		Handler(spaConfigCacheControlMiddleware(ps.endpointHandlerMap[endpointHandlerMapKey]))
   213  }