cmd/relui,internal/relui: serve from sub-paths

This change allows relui to correctly serve from a path, like
build.golang.org/releases. It adds a base-url flag which is used to
prefix all paths referenced in the application.

For golang/go#47401

Change-Id: Ib8f6fe429591ceabfaf0f419e5258a677b375ff8
Reviewed-on: https://go-review.googlesource.com/c/build/+/363975
Trust: Alexander Rakoczy <alex@golang.org>
Run-TryBot: Alexander Rakoczy <alex@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/internal/relui/web_test.go b/internal/relui/web_test.go
index a968410..52dfe92 100644
--- a/internal/relui/web_test.go
+++ b/internal/relui/web_test.go
@@ -116,7 +116,7 @@
 	req := httptest.NewRequest(http.MethodGet, "/", nil)
 	w := httptest.NewRecorder()
 
-	s := NewServer(p, NewWorker(p, &PGListener{p}))
+	s := NewServer(p, NewWorker(p, &PGListener{p}), nil)
 	s.homeHandler(w, req)
 	resp := w.Result()
 
@@ -152,7 +152,7 @@
 			req := httptest.NewRequest(http.MethodGet, u.String(), nil)
 			w := httptest.NewRecorder()
 
-			s := &Server{}
+			s := NewServer(nil, nil, nil)
 			s.newWorkflowHandler(w, req)
 			resp := w.Result()
 
@@ -219,7 +219,7 @@
 			rec := httptest.NewRecorder()
 			q := db.New(p)
 
-			s := NewServer(p, NewWorker(p, &PGListener{p}))
+			s := NewServer(p, NewWorker(p, &PGListener{p}), nil)
 			s.createWorkflowHandler(rec, req)
 			resp := rec.Result()
 
@@ -368,3 +368,55 @@
 func nullString(val string) sql.NullString {
 	return sql.NullString{String: val, Valid: true}
 }
+
+func TestServerBaseLink(t *testing.T) {
+	cases := []struct {
+		desc    string
+		baseURL string
+		target  string
+		want    string
+	}{
+		{
+			desc:   "no baseURL, relative",
+			target: "/workflows",
+			want:   "/workflows",
+		},
+		{
+			desc:   "no baseURL, absolute",
+			target: "https://example.test/something",
+			want:   "https://example.test/something",
+		},
+		{
+			desc:    "absolute baseURL, relative",
+			baseURL: "https://example.test/releases",
+			target:  "/workflows",
+			want:    "https://example.test/releases/workflows",
+		},
+		{
+			desc:    "relative baseURL, relative",
+			baseURL: "/releases",
+			target:  "/workflows",
+			want:    "/releases/workflows",
+		},
+		{
+			desc:    "absolute baseURL, absolute",
+			baseURL: "https://example.test/releases",
+			target:  "https://example.test/something",
+			want:    "https://example.test/something",
+		},
+	}
+	for _, c := range cases {
+		t.Run(c.desc, func(t *testing.T) {
+			base, err := url.Parse(c.baseURL)
+			if err != nil {
+				t.Fatalf("url.Parse(%q) = %v, %v, wanted no error", c.baseURL, base, err)
+			}
+			s := NewServer(nil, nil, base)
+
+			got := s.BaseLink(c.target)
+			if got != c.want {
+				t.Errorf("s.BaseLink(%q) = %q, wanted %q", c.target, got, c.want)
+			}
+		})
+	}
+}