Skip to content

Commit 9076251

Browse files
committed
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 <[email protected]> Run-TryBot: Alexander Rakoczy <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Heschi Kreinick <[email protected]>
1 parent 0a59650 commit 9076251

File tree

7 files changed

+115
-25
lines changed

7 files changed

+115
-25
lines changed

cmd/relui/deployment-prod.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ spec:
2424
- "--"
2525
- "./relui"
2626
- "--listen-https-selfsigned=:444"
27+
- "--base-url=https://build.golang.org/releases"
2728
ports:
2829
- containerPort: 444
2930
env:

cmd/relui/main.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,18 @@ import (
99
"context"
1010
"flag"
1111
"log"
12+
"net/url"
1213

1314
"github.com/jackc/pgx/v4/pgxpool"
1415
"golang.org/x/build/internal/https"
1516
"golang.org/x/build/internal/relui"
1617
)
1718

1819
var (
19-
pgConnect = flag.String("pg-connect", "", "Postgres connection string or URI. If empty, libpq connection defaults are used.")
20-
migrateOnly = flag.Bool("migrate-only", false, "Exit after running migrations. Migrations are run by default.")
20+
baseURL = flag.String("base-url", "", "Prefix URL for routing and links.")
2121
downUp = flag.Bool("migrate-down-up", false, "Run all Up migration steps, then the last down migration step, followed by the final up migration. Exits after completion.")
22+
migrateOnly = flag.Bool("migrate-only", false, "Exit after running migrations. Migrations are run by default.")
23+
pgConnect = flag.String("pg-connect", "", "Postgres connection string or URI. If empty, libpq connection defaults are used.")
2224
)
2325

2426
func main() {
@@ -47,7 +49,14 @@ func main() {
4749
if err := w.ResumeAll(ctx); err != nil {
4850
log.Printf("w.ResumeAll() = %v", err)
4951
}
50-
s := relui.NewServer(db, w)
52+
var base *url.URL
53+
if *baseURL != "" {
54+
base, err = url.Parse(*baseURL)
55+
if err != nil {
56+
log.Fatalf("url.Parse(%q) = %v, %v", *baseURL, base, err)
57+
}
58+
}
59+
s := relui.NewServer(db, w, base)
5160
if err != nil {
5261
log.Fatalf("relui.NewServer() = %v", err)
5362
}

internal/relui/templates/home.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<section class="Workflows">
88
<div class="Workflows-header">
99
<h2>Workflows</h2>
10-
<a href="/workflows/new" class="Button">New</a>
10+
<a href="{{baseLink "/workflows/new"}}" class="Button">New</a>
1111
</div>
1212
<ul class="WorkflowList">
1313
{{range $workflow := .Workflows}}

internal/relui/templates/layout.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<html lang="en">
88
<title>Go Releases</title>
99
<meta name="viewport" content="width=device-width, initial-scale=1" />
10-
<link rel="stylesheet" href="/static/styles.css" />
10+
<link rel="stylesheet" href="{{baseLink "/static/styles.css"}}" />
1111
<body class="Site">
1212
<header class="Site-header">
1313
<div class="Header">

internal/relui/templates/new_workflow.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
{{define "content"}}
77
<section class="NewWorkflow">
88
<h2>New Go Release</h2>
9-
<form action="/workflows/new" method="get">
9+
<form action="{{baseLink "/workflows/new"}}" method="get">
1010
<label for="workflow.name">Workflow:</label>
1111
<select id="workflow.name" name="workflow.name" onchange="this.form.submit()">
1212
<option value="">Select Workflow</option>

internal/relui/web.go

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"log"
1616
"mime"
1717
"net/http"
18+
"net/url"
1819
"path"
1920

2021
"github.com/google/uuid"
@@ -41,40 +42,67 @@ func fileServerHandler(fs fs.FS, next http.Handler) http.Handler {
4142
})
4243
}
4344

44-
var (
45-
homeTmpl = template.Must(template.Must(layoutTmpl.Clone()).ParseFS(templates, "templates/home.html"))
46-
layoutTmpl = template.Must(template.ParseFS(templates, "templates/layout.html"))
47-
newWorkflowTmpl = template.Must(template.Must(layoutTmpl.Clone()).ParseFS(templates, "templates/new_workflow.html"))
48-
)
49-
5045
// Server implements the http handlers for relui.
5146
type Server struct {
52-
db *pgxpool.Pool
53-
m *http.ServeMux
54-
w *Worker
47+
db *pgxpool.Pool
48+
m *http.ServeMux
49+
w *Worker
50+
baseURL *url.URL
51+
52+
homeTmpl *template.Template
53+
newWorkflowTmpl *template.Template
5554
}
5655

5756
// NewServer initializes a server with the provided connection pool.
58-
func NewServer(p *pgxpool.Pool, w *Worker) *Server {
57+
func NewServer(p *pgxpool.Pool, w *Worker, baseURL *url.URL) *Server {
5958
s := &Server{
60-
db: p,
61-
m: new(http.ServeMux),
62-
w: w,
59+
db: p,
60+
m: new(http.ServeMux),
61+
w: w,
62+
baseURL: baseURL,
6363
}
64+
helpers := map[string]interface{}{
65+
"baseLink": s.BaseLink,
66+
}
67+
layout := template.Must(template.New("layout.html").Funcs(helpers).ParseFS(templates, "templates/layout.html"))
68+
s.homeTmpl = template.Must(template.Must(layout.Clone()).Funcs(helpers).ParseFS(templates, "templates/home.html"))
69+
s.newWorkflowTmpl = template.Must(template.Must(layout.Clone()).Funcs(helpers).ParseFS(templates, "templates/new_workflow.html"))
6470
s.m.Handle("/workflows/create", http.HandlerFunc(s.createWorkflowHandler))
6571
s.m.Handle("/workflows/new", http.HandlerFunc(s.newWorkflowHandler))
6672
s.m.Handle("/", fileServerHandler(static, http.HandlerFunc(s.homeHandler)))
6773
return s
6874
}
6975

7076
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
71-
s.m.ServeHTTP(w, r)
77+
if s.baseURL == nil || s.baseURL.Path == "/" {
78+
s.m.ServeHTTP(w, r)
79+
return
80+
}
81+
http.StripPrefix(s.baseURL.Path, s.m)
7282
}
7383

7484
func (s *Server) Serve(port string) error {
7585
return http.ListenAndServe(":"+port, s.m)
7686
}
7787

88+
func (s *Server) BaseLink(target string) string {
89+
if s.baseURL == nil {
90+
return target
91+
}
92+
u, err := url.Parse(target)
93+
if err != nil {
94+
log.Printf("BaseLink: url.Parse(%q) = %v, %v", target, u, err)
95+
return target
96+
}
97+
if u.IsAbs() {
98+
return u.String()
99+
}
100+
u.Scheme = s.baseURL.Scheme
101+
u.Host = s.baseURL.Host
102+
u.Path = path.Join(s.baseURL.Path, u.Path)
103+
return u.String()
104+
}
105+
78106
type homeResponse struct {
79107
Workflows []db.Workflow
80108
WorkflowTasks map[uuid.UUID][]db.Task
@@ -104,7 +132,7 @@ func (s *Server) homeHandler(w http.ResponseWriter, r *http.Request) {
104132
return
105133
}
106134
out := bytes.Buffer{}
107-
if err := homeTmpl.Execute(&out, resp); err != nil {
135+
if err := s.homeTmpl.Execute(&out, resp); err != nil {
108136
log.Printf("homeHandler: %v", err)
109137
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
110138
return
@@ -156,7 +184,7 @@ func (s *Server) newWorkflowHandler(w http.ResponseWriter, r *http.Request) {
156184
Definitions: Definitions(),
157185
Name: r.FormValue("workflow.name"),
158186
}
159-
if err := newWorkflowTmpl.Execute(&out, resp); err != nil {
187+
if err := s.newWorkflowTmpl.Execute(&out, resp); err != nil {
160188
log.Printf("newWorkflowHandler: %v", err)
161189
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
162190
return

internal/relui/web_test.go

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ func TestServerHomeHandler(t *testing.T) {
116116
req := httptest.NewRequest(http.MethodGet, "/", nil)
117117
w := httptest.NewRecorder()
118118

119-
s := NewServer(p, NewWorker(p, &PGListener{p}))
119+
s := NewServer(p, NewWorker(p, &PGListener{p}), nil)
120120
s.homeHandler(w, req)
121121
resp := w.Result()
122122

@@ -152,7 +152,7 @@ func TestServerNewWorkflowHandler(t *testing.T) {
152152
req := httptest.NewRequest(http.MethodGet, u.String(), nil)
153153
w := httptest.NewRecorder()
154154

155-
s := &Server{}
155+
s := NewServer(nil, nil, nil)
156156
s.newWorkflowHandler(w, req)
157157
resp := w.Result()
158158

@@ -219,7 +219,7 @@ func TestServerCreateWorkflowHandler(t *testing.T) {
219219
rec := httptest.NewRecorder()
220220
q := db.New(p)
221221

222-
s := NewServer(p, NewWorker(p, &PGListener{p}))
222+
s := NewServer(p, NewWorker(p, &PGListener{p}), nil)
223223
s.createWorkflowHandler(rec, req)
224224
resp := rec.Result()
225225

@@ -368,3 +368,55 @@ func TestSameUUIDVariant(t *testing.T) {
368368
func nullString(val string) sql.NullString {
369369
return sql.NullString{String: val, Valid: true}
370370
}
371+
372+
func TestServerBaseLink(t *testing.T) {
373+
cases := []struct {
374+
desc string
375+
baseURL string
376+
target string
377+
want string
378+
}{
379+
{
380+
desc: "no baseURL, relative",
381+
target: "/workflows",
382+
want: "/workflows",
383+
},
384+
{
385+
desc: "no baseURL, absolute",
386+
target: "https://example.test/something",
387+
want: "https://example.test/something",
388+
},
389+
{
390+
desc: "absolute baseURL, relative",
391+
baseURL: "https://example.test/releases",
392+
target: "/workflows",
393+
want: "https://example.test/releases/workflows",
394+
},
395+
{
396+
desc: "relative baseURL, relative",
397+
baseURL: "/releases",
398+
target: "/workflows",
399+
want: "/releases/workflows",
400+
},
401+
{
402+
desc: "absolute baseURL, absolute",
403+
baseURL: "https://example.test/releases",
404+
target: "https://example.test/something",
405+
want: "https://example.test/something",
406+
},
407+
}
408+
for _, c := range cases {
409+
t.Run(c.desc, func(t *testing.T) {
410+
base, err := url.Parse(c.baseURL)
411+
if err != nil {
412+
t.Fatalf("url.Parse(%q) = %v, %v, wanted no error", c.baseURL, base, err)
413+
}
414+
s := NewServer(nil, nil, base)
415+
416+
got := s.BaseLink(c.target)
417+
if got != c.want {
418+
t.Errorf("s.BaseLink(%q) = %q, wanted %q", c.target, got, c.want)
419+
}
420+
})
421+
}
422+
}

0 commit comments

Comments
 (0)