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