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}