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