| // Copyright 2017 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. | |
| package main | |
| import ( | |
| "bytes" | |
| "encoding/json" | |
| "fmt" | |
| "io" | |
| "net/http" | |
| "strings" | |
| "time" | |
| "golang.org/x/tools/godoc/static" | |
| ) | |
| type server struct { | |
| mux *http.ServeMux | |
| db store | |
| log logger | |
| cache responseCache | |
| gotip bool // if set, server is using gotip | |
| examples *examplesHandler | |
| // When the executable was last modified. Used for caching headers of compiled assets. | |
| modtime time.Time | |
| } | |
| func newServer(options ...func(s *server) error) (*server, error) { | |
| s := &server{mux: http.NewServeMux()} | |
| for _, o := range options { | |
| if err := o(s); err != nil { | |
| return nil, err | |
| } | |
| } | |
| if s.db == nil { | |
| return nil, fmt.Errorf("must provide an option func that specifies a datastore") | |
| } | |
| if s.log == nil { | |
| return nil, fmt.Errorf("must provide an option func that specifies a logger") | |
| } | |
| if s.examples == nil { | |
| return nil, fmt.Errorf("must provide an option func that sets the examples handler") | |
| } | |
| s.init() | |
| return s, nil | |
| } | |
| func (s *server) init() { | |
| s.mux.HandleFunc("/", s.handleEdit) | |
| s.mux.HandleFunc("/fmt", s.handleFmt) | |
| s.mux.HandleFunc("/version", s.handleVersion) | |
| // TODO(nealpatel): Delete if not used. | |
| s.mux.HandleFunc("/vet", s.commandHandler("vet", vetCheck)) | |
| s.mux.HandleFunc("/compile", s.commandHandler("prog", compileAndRun)) | |
| s.mux.HandleFunc("/share", s.handleShare) | |
| s.mux.HandleFunc("/playground.js", s.handlePlaygroundJS) | |
| s.mux.HandleFunc("/favicon.ico", handleFavicon) | |
| s.mux.HandleFunc("/_ah/health", s.handleHealthCheck) | |
| staticHandler := http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))) | |
| s.mux.Handle("/static/", staticHandler) | |
| s.mux.Handle("/doc/play/", http.StripPrefix("/doc/play/", s.examples)) | |
| } | |
| func (s *server) handlePlaygroundJS(w http.ResponseWriter, r *http.Request) { | |
| w.Header().Set("Content-type", "text/javascript; charset=utf-8") | |
| rd := strings.NewReader(static.Files["playground.js"]) | |
| http.ServeContent(w, r, "playground.js", s.modtime, rd) | |
| } | |
| func handleFavicon(w http.ResponseWriter, r *http.Request) { | |
| http.ServeFile(w, r, "./static/favicon.ico") | |
| } | |
| func (s *server) handleHealthCheck(w http.ResponseWriter, r *http.Request) { | |
| if err := s.healthCheck(r.Context()); err != nil { | |
| http.Error(w, "Health check failed: "+err.Error(), http.StatusInternalServerError) | |
| return | |
| } | |
| fmt.Fprint(w, "ok") | |
| } | |
| func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
| if r.Header.Get("X-Forwarded-Proto") == "http" { | |
| r.URL.Scheme = "https" | |
| r.URL.Host = r.Host | |
| http.Redirect(w, r, r.URL.String(), http.StatusFound) | |
| return | |
| } | |
| if r.Header.Get("X-Forwarded-Proto") == "https" { | |
| w.Header().Set("Strict-Transport-Security", "max-age=31536000; preload") | |
| } | |
| s.mux.ServeHTTP(w, r) | |
| } | |
| // writeJSONResponse JSON-encodes resp and writes to w with the given HTTP | |
| // status. | |
| func (s *server) writeJSONResponse(w http.ResponseWriter, resp any, status int) { | |
| w.Header().Set("Content-Type", "application/json") | |
| var buf bytes.Buffer | |
| if err := json.NewEncoder(&buf).Encode(resp); err != nil { | |
| s.log.Errorf("error encoding response: %v", err) | |
| http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) | |
| return | |
| } | |
| w.WriteHeader(status) | |
| if _, err := io.Copy(w, &buf); err != nil { | |
| s.log.Errorf("io.Copy(w, &buf): %v", err) | |
| return | |
| } | |
| } |