github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/ctl/server/http/server.go (about) 1 // Copyright (c) 2017 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package http 22 23 import ( 24 "net/http" 25 "sync" 26 27 _ "github.com/m3db/m3/src/ctl/generated/ui/statik" // Generated UI statik package 28 mserver "github.com/m3db/m3/src/ctl/server" 29 "github.com/m3db/m3/src/ctl/service" 30 31 "github.com/gorilla/mux" 32 "github.com/rakyll/statik/fs" 33 "go.uber.org/zap" 34 ) 35 36 const ( 37 publicPathPrefix = "/public" 38 staticPathPrefix = "/static" 39 indexFile = "/index.html" 40 ) 41 42 var ( 43 indexPaths = []string{"/", indexFile} 44 ) 45 46 type server struct { 47 server *http.Server 48 services []service.Service 49 logger *zap.Logger 50 wg sync.WaitGroup 51 } 52 53 // NewServer creates a new HTTP server. 54 func NewServer(address string, opts Options, services ...service.Service) (mserver.Server, error) { 55 // Make a copy of the services passed in so they cannot be mutated externally 56 // once the server is constructed. 57 cloned := make([]service.Service, len(services)) 58 copy(cloned, services) 59 handler, err := initRouter(cloned) 60 if err != nil { 61 return nil, err 62 } 63 s := &http.Server{ 64 Addr: address, 65 Handler: handler, 66 ReadTimeout: opts.ReadTimeout(), 67 WriteTimeout: opts.WriteTimeout(), 68 } 69 return &server{ 70 server: s, 71 services: cloned, 72 logger: opts.InstrumentOptions().Logger(), 73 }, nil 74 } 75 76 func (s *server) ListenAndServe() error { 77 s.wg.Add(1) 78 go func() { 79 defer s.wg.Done() 80 if err := s.server.ListenAndServe(); err != nil { 81 s.logger.Error("could not start listening and serving traffic", zap.Error(err)) 82 } 83 }() 84 return nil 85 } 86 87 func (s *server) Close() { 88 s.server.Close() 89 s.wg.Wait() 90 for _, service := range s.services { 91 service.Close() 92 } 93 } 94 95 func initRouter(services []service.Service) (http.Handler, error) { 96 router := mux.NewRouter() 97 if err := registerStaticRoutes(router); err != nil { 98 return nil, err 99 } 100 if err := registerServiceRoutes(router, services); err != nil { 101 return nil, err 102 } 103 return router, nil 104 } 105 106 func registerStaticRoutes(router *mux.Router) error { 107 // Register static and public handler. 108 fileServer, err := fs.New() 109 if err != nil { 110 return err 111 } 112 113 fileServerHandler := http.FileServer(fileServer) 114 router.PathPrefix(publicPathPrefix).Handler(fileServerHandler) 115 router.PathPrefix(staticPathPrefix).Handler(fileServerHandler) 116 117 // Register index handlers. 118 indexHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 119 fileServerHandler.ServeHTTP(w, r) 120 }) 121 for _, path := range indexPaths { 122 router.Path(path).HandlerFunc(indexHandler) 123 } 124 125 return nil 126 } 127 128 func registerServiceRoutes(router *mux.Router, services []service.Service) error { 129 for _, service := range services { 130 pathPrefix := service.URLPrefix() 131 subRouter := router.PathPrefix(pathPrefix).Subrouter() 132 if err := service.RegisterHandlers(subRouter); err != nil { 133 return err 134 } 135 } 136 return nil 137 }