(writer http.ResponseWriter, request *http.Request )
| 210 | var cacheControlRegex = regexp.MustCompile( "[[:space:]]*max-age[[:space:]]*=[[:space:]]*([[:digit:]]+)" ) |
| 211 | |
| 212 | func ( s *Server ) serverList(writer http.ResponseWriter, request *http.Request ) { |
| 213 | if request.Method != "GET" { http.Error( writer, http.StatusText( http.StatusNotFound ), http.StatusNotFound ); return } |
| 214 | select { |
| 215 | case s.connectionOpsLock<-struct{}{}: defer func() { <-s.connectionOpsLock }(); break |
| 216 | case <-time.NewTimer( time.Second ).C: http.Error( writer, http.StatusText( http.StatusConflict ), http.StatusConflict ); return |
| 217 | } |
| 218 | |
| 219 | if slb := s.serverListBytes.Load(); slb != nil { writer.Header().Add( "content-type", "application/json" ); writer.Write( *slb ); log.Println( "sLst: ServerList sent" ); return } |
| 220 | |
| 221 | s.connection.Lock(); defer s.connection.Unlock() |
| 222 | |
| 223 | client := rest.New( s.connection.Config.Rest ) // Create a REST client ( must be a new one since FetchServerList changes client's configuration ) |
| 224 | |
| 225 | dohResolver := doh.New( s.connection.Config.DoH ) // Create a DoH resolver |
| 226 | dohResolver.Init() |
| 227 | client.SetDohResolver(dohResolver) |
| 228 | |
| 229 | plainResolver := plain.New( s.connection.Config.Plain ) // Create a Plain resolver |
| 230 | if err := plainResolver.Init(); err != nil { log.Println( "sLst: [ERR] Plain resolver init failed", err ); http.Error( writer, err.Error(), http.StatusBadGateway ); return } |
| 231 | client.SetPlainResolver(plainResolver) |
| 232 | |
| 233 | ctx, cancel := context.WithTimeout( context.Background(), s.connection.Config.Rest.RestTimeout ) |
| 234 | defer cancel() |
| 235 | |
| 236 | response, headers, err := client.FetchServerList( ctx ) |
| 237 | if err != nil { log.Println( "sLst: [ERR] Server list fetch failed:", err ); http.Error( writer, err.Error(), http.StatusBadGateway ); return } |
| 238 | |
| 239 | if maxAgeMatches := cacheControlRegex.FindStringSubmatch( headers.Get( "cache-control" ) ); len( maxAgeMatches ) > 1 { |
| 240 | if maxAge, err := strconv.ParseInt( maxAgeMatches[1], 10, 64 ); err == nil { |
| 241 | s.serverListBytes.Store( &response ) |
| 242 | ttl := time.Duration( maxAge ) * time.Second |
| 243 | time.AfterFunc( ttl, func() { s.serverListBytes.Store( nil ); log.Println( "sLst: Server list expired" ) } ) |
| 244 | log.Println( "sLst: Caching server list for", ttl ) |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | writer.Header().Add( "content-type", "application/json" ) |
| 249 | writer.Write( response ) |
| 250 | |
| 251 | log.Println( "sLst: ServerList sent" ) |
| 252 | } |
| 253 | |
| 254 | func ( s *Server ) externalIps(writer http.ResponseWriter, request *http.Request ) { |
| 255 | if request.Method != "GET" { http.Error( writer, http.StatusText( http.StatusNotFound ), http.StatusNotFound ); return } |
nothing calls this directly
no test coverage detected