blob: effd257784375fff5b6384780fc4dc9a773273be [file] [log] [blame]
// Copyright 2019 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 (
"log"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"sort"
"strings"
"time"
"golang.org/x/website/internal/backport/html/template"
"golang.org/x/website/internal/backport/osfs"
"golang.org/x/website/internal/web"
"golang.org/x/website/internal/webtest"
)
var discoveryHosts = map[string]string{
"": "pkg.go.dev",
"dev.go.dev": "dev-pkg.go.dev",
"staging.go.dev": "staging-pkg.go.dev",
}
func main() {
dir := "../../_content"
if _, err := os.Stat("go.dev/_content/events.yaml"); err == nil {
// Running in repo root.
dir = "go.dev/_content"
}
h, err := NewHandler(dir)
if err != nil {
log.Fatal(err)
}
h = webtest.HandlerWithCheck(h, "/_readycheck",
filepath.Join(dir, "cmd/frontend/testdata/*.txt"))
addr := ":" + listenPort()
if addr == ":0" {
addr = "localhost:0"
}
l, err := net.Listen("tcp", addr)
if err != nil {
log.Fatalf("net.Listen(%q, %q) = _, %v", "tcp", addr, err)
}
defer l.Close()
log.Printf("Listening on http://%v/\n", l.Addr().String())
log.Print(http.Serve(l, h))
}
func NewHandler(dir string) (http.Handler, error) {
godev := web.NewSite(osfs.DirFS(dir))
godev.Funcs(template.FuncMap{
"newest": newest,
"section": section,
})
mux := http.NewServeMux()
mux.Handle("/", addCSP(godev))
mux.Handle("/explore/", http.StripPrefix("/explore/", redirectHosts(discoveryHosts)))
mux.Handle("learn.go.dev/", http.HandlerFunc(redirectLearn))
return mux, nil
}
// newest returns the pages sorted newest first,
// breaking ties by .linkTitle or else .title.
func newest(pages []web.Page) []web.Page {
out := make([]web.Page, len(pages))
copy(out, pages)
sort.Slice(out, func(i, j int) bool {
pi := out[i]
pj := out[j]
di, _ := pi["date"].(time.Time)
dj, _ := pj["date"].(time.Time)
if !di.Equal(dj) {
return di.After(dj)
}
ti, _ := pi["linkTitle"].(string)
if ti == "" {
ti, _ = pi["title"].(string)
}
tj, _ := pj["linkTitle"].(string)
if tj == "" {
tj, _ = pj["title"].(string)
}
if ti != tj {
return ti < tj
}
return false
})
return out
}
// section returns the site section for the given Page,
// defined as the first path element, or else an empty string.
// For example if p's URL is /x/y/z then section is "x".
func section(p web.Page) string {
u, _ := p["URL"].(string)
if !strings.HasPrefix(u, "/") {
return ""
}
i := strings.Index(u[1:], "/")
if i < 0 {
return ""
}
return u[:1+i+1]
}
func redirectLearn(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://go.dev/learn/"+strings.TrimPrefix(r.URL.Path, "/"), http.StatusMovedPermanently)
}
func listenPort() string {
if p := os.Getenv("PORT"); p != "" {
return p
}
return "0"
}
type redirectHosts map[string]string
func (rh redirectHosts) ServeHTTP(w http.ResponseWriter, r *http.Request) {
u := &url.URL{Scheme: "https", Path: r.URL.Path, RawQuery: r.URL.RawQuery}
if h, ok := rh[r.Host]; ok {
u.Host = h
} else if h, ok := rh[""]; ok {
u.Host = h
} else {
http.NotFound(w, r)
return
}
http.Redirect(w, r, u.String(), http.StatusFound)
}