go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/frontend/server.go (about)

     1  // Copyright 2023 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package frontend
    16  
    17  import (
    18  	"context"
    19  	"net/http"
    20  	"time"
    21  
    22  	"go.chromium.org/luci/common/errors"
    23  	"go.chromium.org/luci/grpc/grpcutil"
    24  	configpb "go.chromium.org/luci/milo/proto/config"
    25  	rdbpb "go.chromium.org/luci/resultdb/proto/v1"
    26  	"go.chromium.org/luci/server"
    27  	"go.chromium.org/luci/server/auth"
    28  	"go.chromium.org/luci/server/middleware"
    29  	"go.chromium.org/luci/server/router"
    30  	"go.chromium.org/luci/server/templates"
    31  	"google.golang.org/grpc/codes"
    32  )
    33  
    34  // HTTPService is the Milo frontend service that serves multiple HTTP endpoints.
    35  // TODO(weiweilin): move other HTTP endpoints to HTTPService.
    36  type HTTPService struct {
    37  	Server *server.Server
    38  
    39  	// GetSettings returns the current setting for milo.
    40  	GetSettings func(c context.Context) (*configpb.Settings, error)
    41  
    42  	// GetResultDBClient returns a ResultDB client for the given context.
    43  	GetResultDBClient func(c context.Context, host string, as auth.RPCAuthorityKind) (rdbpb.ResultDBClient, error)
    44  }
    45  
    46  // RegisterRoutes registers routes explicitly handled by the handler.
    47  func (s *HTTPService) RegisterRoutes() {
    48  	baseMW := router.NewMiddlewareChain()
    49  	baseAuthMW := baseMW.Extend(
    50  		middleware.WithContextTimeout(time.Minute),
    51  		auth.Authenticate(s.Server.CookieAuth),
    52  	)
    53  
    54  	s.Server.Routes.GET("/raw-artifact/*artifactName", baseAuthMW, handleError(s.buildRawArtifactHandler("/raw-artifact/")))
    55  	s.Server.Routes.GET("/configs.js", baseMW, handleError(s.configsJSHandler))
    56  }
    57  
    58  // handleError is a wrapper for a handler so that the handler can return an error
    59  // rather than call ErrorHandler directly.
    60  // This should be used for handlers that render webpages.
    61  func handleError(handler func(c *router.Context) error) func(c *router.Context) {
    62  	return func(c *router.Context) {
    63  		if err := handler(c); err != nil {
    64  			ErrorHandler(c, err)
    65  		}
    66  	}
    67  }
    68  
    69  // ErrorHandler renders an error page for the user.
    70  func ErrorHandler(c *router.Context, err error) {
    71  	code := grpcutil.Code(err)
    72  	switch code {
    73  	case codes.Unauthenticated:
    74  		loginURL, err := auth.LoginURL(c.Request.Context(), c.Request.URL.RequestURI())
    75  		if err == nil {
    76  			http.Redirect(c.Writer, c.Request, loginURL, http.StatusFound)
    77  			return
    78  		}
    79  		errors.Log(
    80  			c.Request.Context(), errors.Annotate(err, "failed to retrieve login URL").Err())
    81  	case codes.OK:
    82  		// All good.
    83  	default:
    84  		errors.Log(c.Request.Context(), err)
    85  	}
    86  
    87  	status := grpcutil.CodeStatus(code)
    88  	c.Writer.WriteHeader(status)
    89  	templates.MustRender(c.Request.Context(), c.Writer, "pages/error.html", templates.Args{
    90  		"Code":    status,
    91  		"Message": err.Error(),
    92  	})
    93  }