diff options
| author | Grégoire Duchêne <gduchene@awhk.org> | 2022-06-19 13:31:49 +0100 |
|---|---|---|
| committer | Grégoire Duchêne <gduchene@awhk.org> | 2022-06-19 13:40:39 +0100 |
| commit | 41d23c22df853b0bdf35e2d0988c8d4c4281d42f (patch) | |
| tree | 48ddc49039d6ceded97914374171157515b004be /pkg | |
| parent | 21456154ef3172490cef72a3b69bf59bb9795e43 (diff) | |
Move redirection logic to a separate packagev0.4.0
Also, add an optional ‘-c’ flag to pass the path to a configuration file
that can be used to specify several matching patterns and replacements.
Diffstat (limited to 'pkg')
| -rw-r--r-- | pkg/redirector/redirector.go | 75 | ||||
| -rw-r--r-- | pkg/redirector/redirector_test.go | 130 | ||||
| -rw-r--r-- | pkg/redirector/template.tpl | 3 |
3 files changed, 208 insertions, 0 deletions
diff --git a/pkg/redirector/redirector.go b/pkg/redirector/redirector.go new file mode 100644 index 0000000..96becb2 --- /dev/null +++ b/pkg/redirector/redirector.go @@ -0,0 +1,75 @@ +package redirector + +import ( + "embed" + "encoding/json" + "log" + "net/http" + "path" + "regexp" + "strings" + "text/template" + + "go.awhk.org/core" +) + +var DefaultTemplate = template.Must(template.ParseFS(fs, "*.tpl")) + +var ( + filter = core.FilterHTTPMethod(http.MethodGet) + + //go:embed *.tpl + fs embed.FS +) + +type Redirector struct { + Template *template.Template + Transformers []Transformer +} + +func (h *Redirector) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if filter(w, req) { + return + } + + pkg := path.Join(req.Host, req.URL.Path) + for _, t := range h.Transformers { + if !t.Pattern.MatchString(pkg) { + continue + } + + if req.URL.Query().Get("go-get") != "1" { + w.Header().Set("Location", "https://pkg.go.dev/"+pkg) + w.WriteHeader(http.StatusFound) + return + } + + repo := t.Pattern.ReplaceAllString(pkg, t.Replacement) + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusOK) + if err := h.Template.Execute(w, TemplateData{pkg, repo, t.VCS}); err != nil { + log.Println("Failed to execute template:", err) + } + return + } + w.WriteHeader(http.StatusNotFound) +} + +type Pattern struct{ *regexp.Regexp } + +func (pp *Pattern) UnmarshalJSON(data []byte) (err error) { + var s string + if err = json.Unmarshal(data, &s); err != nil { + return + } + pp.Regexp, err = regexp.Compile(strings.ReplaceAll(s, `\\`, `\`)) + return +} + +type TemplateData struct{ Package, Repository, VCS string } + +type Transformer struct { + Pattern *Pattern `json:"pattern"` + Replacement string `json:"replacement"` + VCS string `json:"vcs"` +} diff --git a/pkg/redirector/redirector_test.go b/pkg/redirector/redirector_test.go new file mode 100644 index 0000000..8761185 --- /dev/null +++ b/pkg/redirector/redirector_test.go @@ -0,0 +1,130 @@ +package redirector_test + +import ( + "io" + "net/http" + "net/http/httptest" + "regexp" + "testing" + + "go.awhk.org/core" + "go.awhk.org/go-import-redirect/pkg/redirector" +) + +func TestRedirector_ServeHTTP(s *testing.T) { + t := core.T{T: s} + + meta := regexp.MustCompile(`<meta name="go-import" content="(.+?)">`) + redir := &redirector.Redirector{ + Template: redirector.DefaultTemplate, + Transformers: []redirector.Transformer{ + { + Pattern: &redirector.Pattern{regexp.MustCompile("go.example.com/x/baz(?:/.+)?")}, + Replacement: "https://git.example.net/elsewhere/baz", + VCS: "git", + }, + { + Pattern: &redirector.Pattern{regexp.MustCompile("go.example.com/x/([^/]+)(?:/.+)?")}, + Replacement: "https://git.example.net/y/$1", + VCS: "git", + }, + { + Pattern: &redirector.Pattern{regexp.MustCompile("go.example.com/x/qux(?:/.+)?")}, + Replacement: "https://git.example.net/elsewhere/qux", + VCS: "git", + }, + }, + } + + for _, tc := range []struct { + name string + method string + url string + + expGetStatusCode int + expGetGoImport string + expVisitStatusCode int + expVisitLocation string + }{ + { + name: "Match", + method: http.MethodGet, + url: "https://go.example.com/x/foo", + + expGetStatusCode: http.StatusOK, + expGetGoImport: "go.example.com/x/foo git https://git.example.net/y/foo", + expVisitStatusCode: http.StatusFound, + expVisitLocation: "https://pkg.go.dev/go.example.com/x/foo", + }, + { + name: "MatchDirectory", + method: http.MethodGet, + url: "https://go.example.com/x/foo/bar", + + expGetStatusCode: http.StatusOK, + expGetGoImport: "go.example.com/x/foo/bar git https://git.example.net/y/foo", + expVisitStatusCode: http.StatusFound, + expVisitLocation: "https://pkg.go.dev/go.example.com/x/foo/bar", + }, + { + name: "MatchIgnored", + method: http.MethodGet, + url: "https://go.example.com/x/qux", + + expGetStatusCode: http.StatusOK, + expGetGoImport: "go.example.com/x/qux git https://git.example.net/y/qux", + expVisitStatusCode: http.StatusFound, + expVisitLocation: "https://pkg.go.dev/go.example.com/x/qux", + }, + { + name: "MatchSpecific", + method: http.MethodGet, + url: "https://go.example.com/x/baz", + + expGetStatusCode: http.StatusOK, + expGetGoImport: "go.example.com/x/baz git https://git.example.net/elsewhere/baz", + expVisitStatusCode: http.StatusFound, + expVisitLocation: "https://pkg.go.dev/go.example.com/x/baz", + }, + { + name: "BadMethod", + method: http.MethodPost, + url: "https://go.example.com/x/baz", + + expGetStatusCode: http.StatusMethodNotAllowed, + expVisitStatusCode: http.StatusMethodNotAllowed, + }, + } { + t.Run("Get"+tc.name, func(t *core.T) { + var ( + req = httptest.NewRequest(tc.method, tc.url+"?go-get=1", nil) + w = httptest.NewRecorder() + ) + redir.ServeHTTP(w, req) + + resp := w.Result() + t.AssertEqual(tc.expGetStatusCode, resp.StatusCode) + t.AssertEqual("", resp.Header.Get("Location")) + + match := meta.FindSubmatch(core.Must(io.ReadAll(resp.Body))) + if tc.expGetGoImport == "" { + t.AssertEqual(0, len(match)) + return + } + if t.AssertEqual(2, len(match)) { + t.AssertEqual(tc.expGetGoImport, string(match[1])) + } + }) + t.Run("Visit"+tc.name, func(t *core.T) { + var ( + req = httptest.NewRequest(tc.method, tc.url, nil) + w = httptest.NewRecorder() + ) + redir.ServeHTTP(w, req) + + resp := w.Result() + t.AssertEqual(tc.expVisitStatusCode, resp.StatusCode) + t.AssertEqual(tc.expVisitLocation, resp.Header.Get("Location")) + }) + } +} diff --git a/pkg/redirector/template.tpl b/pkg/redirector/template.tpl new file mode 100644 index 0000000..422024a --- /dev/null +++ b/pkg/redirector/template.tpl @@ -0,0 +1,3 @@ +<!doctype html> +<meta name="go-import" content="{{.Package}} {{.VCS}} {{.Repository}}"> +<title>go-import-redirect</title> |
