github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/ui/server.go (about) 1 /* 2 * Copyright (C) 2019 The "MysteriumNetwork/node" Authors. 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 package ui 19 20 import ( 21 "context" 22 "fmt" 23 "net/http" 24 "strings" 25 "time" 26 27 "github.com/mysteriumnetwork/node/ui/versionmanager" 28 29 "github.com/gin-contrib/cors" 30 "github.com/gin-gonic/gin" 31 godvpnweb "github.com/mysteriumnetwork/go-dvpn-web/v2" 32 "github.com/mysteriumnetwork/node/requests" 33 "github.com/mysteriumnetwork/node/ui/discovery" 34 "github.com/rs/zerolog/log" 35 ) 36 37 // Server represents our web UI server 38 type Server struct { 39 servers []*http.Server 40 discovery discovery.LANDiscovery 41 reverseProxy gin.HandlerFunc 42 uiVersionConfig versionmanager.NodeUIVersionConfig 43 } 44 45 type jwtAuthenticator interface { 46 ValidateToken(token string) (bool, error) 47 } 48 49 var corsConfig = cors.Config{ 50 AllowMethods: []string{ 51 "GET", 52 "HEAD", 53 "POST", 54 "PUT", 55 "DELETE", 56 "CONNECT", 57 "OPTIONS", 58 "TRACE", 59 "PATCH", 60 }, 61 AllowHeaders: []string{ 62 "Origin", 63 "Content-Length", 64 "Content-Type", 65 "Cache-Control", 66 "X-XSRF-TOKEN", 67 "X-CSRF-TOKEN", 68 }, 69 AllowCredentials: true, 70 AllowOriginFunc: func(origin string) bool { 71 return true 72 }, 73 } 74 75 // NewServer creates a new instance of the server for the given port 76 // you can chain addresses with ',' i.e. "192.168.0.1,127.0.0.1" 77 func NewServer( 78 bindAddress string, 79 port int, 80 tequilapiAddress string, 81 tequilapiPort int, 82 authenticator jwtAuthenticator, 83 httpClient *requests.HTTPClient, 84 uiVersionConfig versionmanager.NodeUIVersionConfig, 85 ) *Server { 86 gin.SetMode(gin.ReleaseMode) 87 reverseProxy := ReverseTequilapiProxy(tequilapiAddress, tequilapiPort, authenticator) 88 89 var r *gin.Engine 90 version, err := uiVersionConfig.Version() 91 92 var assets http.FileSystem = godvpnweb.Assets 93 if err != nil || version == versionmanager.BundledVersionName { 94 log.Warn().Err(err).Msg("could not read node ui version config, falling back to bundled version") 95 } else { 96 assets = http.Dir(uiVersionConfig.UIBuildPath(version)) 97 } 98 99 r = ginEngine(reverseProxy, assets) 100 101 addrs := strings.Split(bindAddress, ",") 102 103 var srvs []*http.Server 104 for _, addr := range addrs { 105 s := &http.Server{ 106 Addr: fmt.Sprintf("%v:%v", addr, port), 107 Handler: r, 108 } 109 srvs = append(srvs, s) 110 } 111 112 return &Server{ 113 servers: srvs, 114 discovery: discovery.NewLANDiscoveryService(port, httpClient), 115 reverseProxy: reverseProxy, 116 uiVersionConfig: uiVersionConfig, 117 } 118 } 119 120 func ginEngine(reverseProxy gin.HandlerFunc, dir http.FileSystem) *gin.Engine { 121 gin.SetMode(gin.ReleaseMode) 122 r := gin.New() 123 r.Use(gin.Recovery()) 124 r.NoRoute(reverseProxy) 125 r.Use(cors.New(corsConfig)) 126 127 r.StaticFS("/", dir) 128 129 return r 130 } 131 132 // SwitchUI switch nodeUI version 133 func (s *Server) SwitchUI(path string) { 134 var assets http.FileSystem = http.Dir(path) 135 if path == versionmanager.BundledVersionName { 136 assets = godvpnweb.Assets 137 } 138 for i := range s.servers { 139 s.servers[i].Handler = ginEngine(s.reverseProxy, assets) 140 } 141 } 142 143 // Serve starts servers 144 func (s *Server) Serve() { 145 go func() { 146 err := s.discovery.Start() 147 if err != nil { 148 log.Error().Err(err).Msg("Failed to start local discovery service") 149 } 150 }() 151 152 for _, srv := range s.servers { 153 go startListen(srv) 154 } 155 } 156 157 func startListen(s *http.Server) { 158 log.Info().Msgf("UI starting on: %s", s.Addr) 159 err := s.ListenAndServe() 160 if err != http.ErrServerClosed { 161 log.Err(err).Msg("UI server crashed") 162 } 163 } 164 165 // Stop stops servers 166 func (s *Server) Stop() { 167 err := s.discovery.Stop() 168 if err != nil { 169 log.Error().Err(err).Msg("Failed to stop local discovery service") 170 } 171 172 // give the server a few seconds to shut down properly in case a request is waiting somewhere 173 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 174 defer cancel() 175 for _, srv := range s.servers { 176 err = srv.Shutdown(ctx) 177 log.Info().Err(err).Msg("Server stopped") 178 } 179 }