blob: 72b376715a9decb6e0996dfd3c1062cf34e472e6 [file] [log] [blame] [edit]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Frontend service handles home page, API and redirects. Static files are
// served by GAE directly (configured in app.yaml).
package main
import (
"net/http"
"os"
"strings"
"go.chromium.org/luci/config/server/cfgmodule"
"go.chromium.org/luci/grpc/prpc"
"go.chromium.org/luci/server"
"go.chromium.org/luci/server/gaeemulation"
"go.chromium.org/luci/server/module"
"go.chromium.org/luci/server/router"
"go.chromium.org/luci/server/templates"
"go.chromium.org/infra/appengine/cr-rev/frontend/api/v1"
"go.chromium.org/infra/appengine/cr-rev/frontend/redirect"
)
const templatePath = "templates"
// handleIndex serves homepage of cr-rev
func handleIndex(c *router.Context) {
templates.MustRender(
c.Request.Context(), c.Writer, "pages/index.html", templates.Args{})
}
// Assembles the URL to a CL on the Gerrit instance with given base URL. If no
// CL is given in the path, returns the URL to the Gerrit instance's dashboard.
func getGerritUrl(base string, path string) string {
path = strings.TrimLeft(path, "/")
if path == "" {
return base + "dashboard/self"
}
return base + "c/" + path
}
// handlePublicGerritRedirect redirects user to a CL on chromium-review
func handlePublicGerritRedirect(c *router.Context) {
path := c.Params.ByName("path")
url := getGerritUrl("https://chromium-review.googlesource.com/", path)
http.Redirect(
c.Writer, c.Request, url, http.StatusPermanentRedirect)
}
// handleInternalGerritRedirect redirects user to a CL on
// chrome-internal-review.
func handleInternalGerritRedirect(c *router.Context) {
path := c.Params.ByName("path")
url := getGerritUrl("https://chrome-internal-review.googlesource.com/", path)
http.Redirect(
c.Writer, c.Request, url, http.StatusPermanentRedirect)
}
// handleRedirect redirects user base on redirect rules. This is a catch-all
// redirect handler (e.g. crrev.com/3, crrev.com/{commit hash}). To add more
// rules, look at redirect package.
func handleRedirect(redirectRules *redirect.Rules, c *router.Context) {
url, _, err := redirectRules.FindRedirectURL(c.Request.Context(), c.Request.RequestURI)
switch err {
case nil:
http.Redirect(
c.Writer, c.Request, url, http.StatusPermanentRedirect)
case redirect.ErrNoMatch:
http.NotFound(c.Writer, c.Request)
default:
http.Error(
c.Writer, err.Error(), http.StatusInternalServerError)
}
}
func main() {
mw := router.MiddlewareChain{}
mw = mw.Extend(templates.WithTemplates(&templates.Bundle{
Loader: templates.FileSystemLoader(os.DirFS(templatePath)),
DefaultTemplate: "base",
}))
modules := []module.Module{
cfgmodule.NewModuleFromFlags(),
gaeemulation.NewModuleFromFlags(),
}
server.Main(nil, modules, func(srv *server.Server) error {
redirect := redirect.NewRules(redirect.NewGitilesRedirect())
srv.Routes.Handle("GET", "/i/*path", mw, handleInternalGerritRedirect)
srv.Routes.Handle("GET", "/c/*path", mw, handlePublicGerritRedirect)
srv.Routes.GET("/", mw, handleIndex)
server := api.NewServer(redirect)
// Host pRPC servers.
srv.ConfigurePRPC(func(s *prpc.Server) {
// CORS requests are fine because
// 1. we don't use any cookie/TLS based authentication for pRPC endpoints,
// and
// 2. none of the pRPC endpoints trigger mutation anyway.
s.AccessControl = prpc.AllowOriginAll
// TODO(crbug/1082369): Remove this workaround once field masks can be decoded.
s.EnableNonStandardFieldMasks = true
})
api.RegisterCrrevServer(srv, server)
// Host HTTP servers.
apiV1 := srv.Routes.Subrouter("/_ah/api/crrev/v1")
api.NewRESTServer(apiV1, server)
// NotFound is used as catch-all.
srv.Routes.NotFound(mw, func(c *router.Context) {
handleRedirect(redirect, c)
})
return nil
})
}