Parent directory

goget.go

2860 bytes
  1package web
  2
  3import (
  4	"context"
  5	"net/http"
  6	"net/url"
  7	"path"
  8	"text/template"
  9
 10	"charm.land/log/v2"
 11	"github.com/charmbracelet/soft-serve/pkg/backend"
 12	"github.com/charmbracelet/soft-serve/pkg/config"
 13	"github.com/charmbracelet/soft-serve/pkg/utils"
 14	"github.com/gorilla/mux"
 15	"github.com/prometheus/client_golang/prometheus"
 16	"github.com/prometheus/client_golang/prometheus/promauto"
 17)
 18
 19var goGetCounter = promauto.NewCounterVec(prometheus.CounterOpts{
 20	Namespace: "soft_serve",
 21	Subsystem: "http",
 22	Name:      "go_get_total",
 23	Help:      "The total number of go get requests",
 24}, []string{"repo"})
 25
 26var repoIndexHTMLTpl = template.Must(template.New("index").Parse(`<!DOCTYPE html>
 27<html lang="en">
 28<head>
 29    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
 30    <meta http-equiv="refresh" content="0; url=https://godoc.org/{{ .ImportRoot }}/{{.Repo}}">
 31    <meta name="go-import" content="{{ .ImportRoot }}/{{ .Repo }} git {{ .Config.HTTP.PublicURL }}/{{ .Repo }}.git">
 32</head>
 33<body>
 34Redirecting to docs at <a href="https://godoc.org/{{ .ImportRoot }}/{{ .Repo }}">godoc.org/{{ .ImportRoot }}/{{ .Repo }}</a>...
 35</body>
 36</html>
 37`))
 38
 39// GoGetController registers Go import metadata routes.
 40func GoGetController(_ context.Context, r *mux.Router) {
 41	basePrefix := "/{repo:.*}"
 42	r.Handle(basePrefix, withParams(withAccess(http.HandlerFunc(GoGetHandler)))).
 43		Methods(http.MethodGet).
 44		Queries("go-get", "1")
 45}
 46
 47// GoGetHandler handles go get requests.
 48func GoGetHandler(w http.ResponseWriter, r *http.Request) {
 49	ctx := r.Context()
 50	cfg := config.FromContext(ctx)
 51	be := backend.FromContext(ctx)
 52	logger := log.FromContext(ctx)
 53	repo := mux.Vars(r)["repo"]
 54
 55	// Handle go get requests.
 56	//
 57	// Always return a 200 status code, even if the repo path doesn't exist.
 58	// It will try to find the repo by walking up the path until it finds one.
 59	// If it can't find one, it will return a 404.
 60	//
 61	// https://golang.org/cmd/go/#hdr-Remote_import_paths
 62	// https://go.dev/ref/mod#vcs-branch
 63	if r.URL.Query().Get("go-get") == "1" {
 64		repo := repo
 65		importRoot, err := url.Parse(cfg.HTTP.PublicURL)
 66		if err != nil {
 67			http.Error(w, err.Error(), http.StatusInternalServerError)
 68			return
 69		}
 70
 71		// find the repo
 72		for {
 73			if _, err := be.Repository(ctx, repo); err == nil {
 74				break
 75			}
 76
 77			if repo == "" || repo == "." || repo == "/" {
 78				renderNotFound(w, r)
 79				return
 80			}
 81
 82			repo = path.Dir(repo)
 83		}
 84
 85		if err := repoIndexHTMLTpl.Execute(w, struct {
 86			Repo       string
 87			Config     *config.Config
 88			ImportRoot string
 89		}{
 90			Repo:       utils.SanitizeRepo(repo),
 91			Config:     cfg,
 92			ImportRoot: importRoot.Host,
 93		}); err != nil {
 94			logger.Error("failed to render go get template", "err", err)
 95			renderInternalServerError(w, r)
 96			return
 97		}
 98
 99		goGetCounter.WithLabelValues(repo).Inc()
100		return
101	}
102
103	renderNotFound(w, r)
104}