github.com/google/cloudprober@v0.11.3/servers/http/http.go (about) 1 // Copyright 2017-2019 The Cloudprober Authors. 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 http implements an HTTP server that simply returns 'ok' for any URL and sends stats 16 // on a string channel. This is used by cloudprober to act as the backend for the HTTP based 17 // probes. 18 package http 19 20 import ( 21 "context" 22 "crypto/tls" 23 "errors" 24 "fmt" 25 "net" 26 "net/http" 27 "strconv" 28 "time" 29 30 "github.com/google/cloudprober/logger" 31 "github.com/google/cloudprober/metrics" 32 "github.com/google/cloudprober/probes/probeutils" 33 configpb "github.com/google/cloudprober/servers/http/proto" 34 "github.com/google/cloudprober/sysvars" 35 "github.com/google/cloudprober/targets/endpoint" 36 "github.com/google/cloudprober/targets/lameduck" 37 ) 38 39 const statsExportInterval = 10 * time.Second 40 41 // OK is the response returned as successful indication by "/", and "/healthcheck". 42 var OK = "ok" 43 44 // statsKeeper manages the stats and exports those stats at a regular basis. 45 // Currently we only maintain the number of requests received per URL. 46 func (s *Server) statsKeeper(name string) { 47 doExport := time.Tick(s.statsInterval) 48 for { 49 select { 50 case ts := <-doExport: 51 em := metrics.NewEventMetrics(ts). 52 AddMetric("req", s.reqMetric). 53 AddLabel("module", name) 54 s.dataChan <- em 55 } 56 } 57 } 58 59 // lameduckStatus fetches the global list of lameduck targets and returns: 60 // - (true, nil) if this machine is in that list 61 // - (false, nil) if not 62 // - (false, error) upon failure to fetch the list. 63 func (s *Server) lameduckStatus() (bool, error) { 64 if s.ldLister == nil { 65 return false, errors.New("lameduck lister not initialized") 66 } 67 68 lameducksList := s.ldLister.ListEndpoints() 69 for _, ep := range lameducksList { 70 if s.instanceName == ep.Name { 71 return true, nil 72 } 73 } 74 return false, nil 75 } 76 77 func (s *Server) lameduckHandler(w http.ResponseWriter) { 78 if lameduck, err := s.lameduckStatus(); err != nil { 79 fmt.Fprintf(w, "HTTP Server: Error getting lameduck status: %v", err) 80 } else { 81 w.Write([]byte(strconv.FormatBool(lameduck))) 82 } 83 } 84 85 func (s *Server) healthcheckHandler(w http.ResponseWriter) { 86 lameduck, err := s.lameduckStatus() 87 if err != nil { 88 s.l.Error(err.Error()) 89 } 90 if lameduck { 91 http.Error(w, "lameduck", http.StatusServiceUnavailable) 92 } else { 93 w.Write([]byte(OK)) 94 } 95 } 96 97 func (s *Server) metadataHandler(w http.ResponseWriter, r *http.Request) { 98 varNames, ok := r.URL.Query()["var"] 99 if !ok || len(varNames) == 0 { 100 http.Error(w, "not found", http.StatusNotFound) 101 return 102 } 103 val, ok := s.sysVars[varNames[0]] 104 if !ok { 105 http.Error(w, fmt.Sprintf("'%s' not found", varNames[0]), http.StatusNotFound) 106 return 107 } 108 w.Write([]byte(val)) 109 } 110 111 func (s *Server) handler(w http.ResponseWriter, r *http.Request) { 112 switch r.URL.Path { 113 case "/lameduck": 114 s.lameduckHandler(w) 115 case "/healthcheck": 116 s.healthcheckHandler(w) 117 case "/metadata": 118 s.metadataHandler(w, r) 119 default: 120 res, ok := s.staticURLResTable[r.URL.Path] 121 if !ok { 122 http.Error(w, "not found", http.StatusNotFound) 123 return 124 } 125 w.Write(res) 126 } 127 s.reqMetric.IncKey(r.URL.Path) 128 } 129 130 // Server implements a basic single-threaded, fast response web server. 131 type Server struct { 132 c *configpb.ServerConf 133 ln net.Listener 134 instanceName string 135 sysVars map[string]string 136 staticURLResTable map[string][]byte 137 reqMetric *metrics.Map 138 dataChan chan<- *metrics.EventMetrics 139 statsInterval time.Duration 140 ldLister endpoint.Lister // Lameduck lister 141 l *logger.Logger 142 } 143 144 // New returns a Server. 145 func New(initCtx context.Context, c *configpb.ServerConf, l *logger.Logger) (*Server, error) { 146 ln, err := net.Listen("tcp", fmt.Sprintf(":%d", int(c.GetPort()))) 147 if err != nil { 148 return nil, err 149 } 150 151 // If we are not able get the default lameduck lister, we only log a warning. 152 ldLister, err := lameduck.GetDefaultLister() 153 if err != nil { 154 l.Warning(err.Error()) 155 } 156 157 if c.GetProtocol() == configpb.ServerConf_HTTPS { 158 if c.GetTlsCertFile() == "" || c.GetTlsKeyFile() == "" { 159 return nil, errors.New("tls_cert_file and tls_key_file are required for HTTPS servers") 160 } 161 } 162 163 // Cleanup listener if initCtx is canceled. 164 go func() { 165 <-initCtx.Done() 166 ln.Close() 167 }() 168 169 sysVars := sysvars.Vars() 170 171 return &Server{ 172 c: c, 173 l: l, 174 ln: ln, 175 ldLister: ldLister, 176 sysVars: sysVars, 177 reqMetric: metrics.NewMap("url", metrics.NewInt(0)), 178 statsInterval: statsExportInterval, 179 instanceName: sysvars.Vars()["instance"], 180 staticURLResTable: map[string][]byte{ 181 "/": []byte(OK), 182 "/instance": []byte(sysVars["instance"]), 183 }, 184 }, nil 185 } 186 187 // Start starts a simple HTTP server on a given port. This function returns 188 // only if there is an error. 189 func (s *Server) Start(ctx context.Context, dataChan chan<- *metrics.EventMetrics) error { 190 s.dataChan = dataChan 191 192 laddr := s.ln.Addr().String() 193 go s.statsKeeper(fmt.Sprintf("http-server-%s", laddr)) 194 195 for _, dh := range s.c.GetPatternDataHandler() { 196 payload := make([]byte, int(dh.GetResponseSize())) 197 probeutils.PatternPayload(payload, []byte(dh.GetPattern())) 198 s.staticURLResTable[fmt.Sprintf("/data_%d", dh.GetResponseSize())] = payload 199 } 200 201 // Not using default server mux as we may run multiple HTTP servers, e.g. for testing. 202 serverMux := http.NewServeMux() 203 serverMux.HandleFunc("/", s.handler) 204 s.l.Infof("Starting HTTP server at: %s", laddr) 205 srv := &http.Server{ 206 Addr: laddr, 207 Handler: serverMux, 208 ReadTimeout: time.Duration(s.c.GetReadTimeoutMs()) * time.Millisecond, 209 WriteTimeout: time.Duration(s.c.GetWriteTimeoutMs()) * time.Millisecond, 210 IdleTimeout: time.Duration(s.c.GetIdleTimeoutMs()) * time.Millisecond, 211 } 212 213 // Setup a background function to close server if context is canceled. 214 go func() { 215 <-ctx.Done() 216 srv.Close() 217 }() 218 219 // HTTP/2 is enabled by default for HTTPS servers. To disable it, TLSNextProto 220 // should be non-nil and set to an empty dict. 221 if s.c.GetDisableHttp2() { 222 srv.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler)) 223 } 224 225 // Following returns only in case of an error. 226 if s.c.GetProtocol() == configpb.ServerConf_HTTP { 227 return srv.Serve(s.ln) 228 } 229 return srv.ServeTLS(s.ln, s.c.GetTlsCertFile(), s.c.GetTlsKeyFile()) 230 }