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 }