| // Copyright 2023 The Go Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style | 
 | // license that can be found in the LICENSE file. | 
 |  | 
 | // Outyet is a web server that announces whether or not a particular Go version | 
 | // has been tagged. | 
 | package main | 
 |  | 
 | import ( | 
 | 	"expvar" | 
 | 	"flag" | 
 | 	"fmt" | 
 | 	"html/template" | 
 | 	"log" | 
 | 	"net/http" | 
 | 	"sync" | 
 | 	"time" | 
 | ) | 
 |  | 
 | // Command-line flags. | 
 | var ( | 
 | 	httpAddr   = flag.String("http", "localhost:8080", "Listen address") | 
 | 	pollPeriod = flag.Duration("poll", 5*time.Second, "Poll period") | 
 | 	version    = flag.String("version", "1.4", "Go version") | 
 | ) | 
 |  | 
 | const baseChangeURL = "https://go.googlesource.com/go/+/" | 
 |  | 
 | func main() { | 
 | 	flag.Parse() | 
 | 	changeURL := fmt.Sprintf("%sgo%s", baseChangeURL, *version) | 
 | 	http.Handle("/", NewServer(*version, changeURL, *pollPeriod)) | 
 | 	log.Printf("serving http://%s", *httpAddr) | 
 | 	log.Fatal(http.ListenAndServe(*httpAddr, nil)) | 
 | } | 
 |  | 
 | // Exported variables for monitoring the server. | 
 | // These are exported via HTTP as a JSON object at /debug/vars. | 
 | var ( | 
 | 	hitCount       = expvar.NewInt("hitCount") | 
 | 	pollCount      = expvar.NewInt("pollCount") | 
 | 	pollError      = expvar.NewString("pollError") | 
 | 	pollErrorCount = expvar.NewInt("pollErrorCount") | 
 | ) | 
 |  | 
 | // Server implements the outyet server. | 
 | // It serves the user interface (it's an http.Handler) | 
 | // and polls the remote repository for changes. | 
 | type Server struct { | 
 | 	version string | 
 | 	url     string | 
 | 	period  time.Duration | 
 |  | 
 | 	mu  sync.RWMutex // protects the yes variable | 
 | 	yes bool | 
 | } | 
 |  | 
 | // NewServer returns an initialized outyet server. | 
 | func NewServer(version, url string, period time.Duration) *Server { | 
 | 	s := &Server{version: version, url: url, period: period} | 
 | 	go s.poll() | 
 | 	return s | 
 | } | 
 |  | 
 | // poll polls the change URL for the specified period until the tag exists. | 
 | // Then it sets the Server's yes field true and exits. | 
 | func (s *Server) poll() { | 
 | 	for !isTagged(s.url) { | 
 | 		pollSleep(s.period) | 
 | 	} | 
 | 	s.mu.Lock() | 
 | 	s.yes = true | 
 | 	s.mu.Unlock() | 
 | 	pollDone() | 
 | } | 
 |  | 
 | // Hooks that may be overridden for integration tests. | 
 | var ( | 
 | 	pollSleep = time.Sleep | 
 | 	pollDone  = func() {} | 
 | ) | 
 |  | 
 | // isTagged makes an HTTP HEAD request to the given URL and reports whether it | 
 | // returned a 200 OK response. | 
 | func isTagged(url string) bool { | 
 | 	pollCount.Add(1) | 
 | 	r, err := http.Head(url) | 
 | 	if err != nil { | 
 | 		log.Print(err) | 
 | 		pollError.Set(err.Error()) | 
 | 		pollErrorCount.Add(1) | 
 | 		return false | 
 | 	} | 
 | 	return r.StatusCode == http.StatusOK | 
 | } | 
 |  | 
 | // ServeHTTP implements the HTTP user interface. | 
 | func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { | 
 | 	hitCount.Add(1) | 
 | 	s.mu.RLock() | 
 | 	data := struct { | 
 | 		URL     string | 
 | 		Version string | 
 | 		Yes     bool | 
 | 	}{ | 
 | 		s.url, | 
 | 		s.version, | 
 | 		s.yes, | 
 | 	} | 
 | 	s.mu.RUnlock() | 
 | 	err := tmpl.Execute(w, data) | 
 | 	if err != nil { | 
 | 		log.Print(err) | 
 | 	} | 
 | } | 
 |  | 
 | // tmpl is the HTML template that drives the user interface. | 
 | var tmpl = template.Must(template.New("tmpl").Parse(` | 
 | <!DOCTYPE html><html><body><center> | 
 | 	<h2>Is Go {{.Version}} out yet?</h2> | 
 | 	<h1> | 
 | 	{{if .Yes}} | 
 | 		<a href="{{.URL}}">YES!</a> | 
 | 	{{else}} | 
 | 		No. :-( | 
 | 	{{end}} | 
 | 	</h1> | 
 | </center></body></html> | 
 | `)) |