go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/examples/appengine/helloworld_v2/main.go (about)

     1  // Copyright 2019 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 main
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net/http"
    21  	"os"
    22  	"time"
    23  
    24  	"github.com/gomodule/redigo/redis"
    25  	"go.opentelemetry.io/otel"
    26  	"google.golang.org/protobuf/types/known/emptypb"
    27  
    28  	"go.chromium.org/luci/auth/identity"
    29  	"go.chromium.org/luci/common/logging"
    30  
    31  	"go.chromium.org/luci/server"
    32  	"go.chromium.org/luci/server/analytics"
    33  	"go.chromium.org/luci/server/auth"
    34  	"go.chromium.org/luci/server/auth/openid"
    35  	"go.chromium.org/luci/server/cron"
    36  	"go.chromium.org/luci/server/encryptedcookies"
    37  	"go.chromium.org/luci/server/gaeemulation"
    38  	"go.chromium.org/luci/server/loginsessions"
    39  	"go.chromium.org/luci/server/mailer"
    40  	"go.chromium.org/luci/server/module"
    41  	"go.chromium.org/luci/server/redisconn"
    42  	"go.chromium.org/luci/server/router"
    43  	"go.chromium.org/luci/server/secrets"
    44  	"go.chromium.org/luci/server/templates"
    45  	"go.chromium.org/luci/server/tq"
    46  
    47  	"go.chromium.org/luci/examples/k8s/helloworld/apipb"
    48  
    49  	// Use datastore as a backend for auth session and TQ transactions.
    50  	_ "go.chromium.org/luci/server/encryptedcookies/session/datastore"
    51  	_ "go.chromium.org/luci/server/tq/txn/datastore"
    52  )
    53  
    54  var tracer = otel.Tracer("go.chromium.org/luci/example")
    55  
    56  func main() {
    57  	// Additional modules that extend the server functionality.
    58  	modules := []module.Module{
    59  		analytics.NewModuleFromFlags(),
    60  		cron.NewModuleFromFlags(),
    61  		encryptedcookies.NewModuleFromFlags(),
    62  		gaeemulation.NewModuleFromFlags(),
    63  		loginsessions.NewModuleFromFlags(),
    64  		mailer.NewModuleFromFlags(),
    65  		redisconn.NewModuleFromFlags(),
    66  		secrets.NewModuleFromFlags(),
    67  		tq.NewModuleFromFlags(),
    68  	}
    69  
    70  	server.Main(nil, modules, func(srv *server.Server) error {
    71  		// When running locally, serve static files ourself.
    72  		if !srv.Options.Prod {
    73  			srv.Routes.Static("/static", nil, http.Dir("./static"))
    74  		}
    75  
    76  		// gRPC example.
    77  		apipb.RegisterGreeterServer(srv, &greeterServer{})
    78  
    79  		// Logging and tracing example.
    80  		srv.Routes.GET("/log", nil, func(c *router.Context) {
    81  			logging.Debugf(c.Request.Context(), "Hello debug world")
    82  
    83  			ctx, span := tracer.Start(c.Request.Context(), "Testing")
    84  			logging.Infof(ctx, "Hello info world")
    85  			time.Sleep(100 * time.Millisecond)
    86  			span.End()
    87  
    88  			logging.Warningf(c.Request.Context(), "Hello warning world")
    89  			c.Writer.Write([]byte("Hello, world"))
    90  
    91  			logging.WithError(fmt.Errorf("boom")).Errorf(c.Request.Context(), "Hello error world")
    92  		})
    93  
    94  		// Redis example.
    95  		//
    96  		// To run Redis for tests locally (in particular on OSX):
    97  		//   docker run --name redis -p 6379:6379 --restart always --detach redis
    98  		//
    99  		// Then launch the example with "... -redis-addr :6379".
   100  		//
   101  		// Note that it makes Redis port available on 0.0.0.0. This is a necessity
   102  		// when using Docker-for-Mac. Don't put any sensitive stuff there (or make
   103  		// sure your firewall is configured to block external connections).
   104  		srv.Routes.GET("/redis", nil, func(c *router.Context) {
   105  			conn, err := redisconn.Get(c.Request.Context())
   106  			if err != nil {
   107  				http.Error(c.Writer, err.Error(), 500)
   108  				return
   109  			}
   110  			defer conn.Close()
   111  			n, err := redis.Int(conn.Do("INCR", "testKey"))
   112  			if err != nil {
   113  				http.Error(c.Writer, err.Error(), 500)
   114  				return
   115  			}
   116  			fmt.Fprintf(c.Writer, "%d", n)
   117  		})
   118  
   119  		// OpenID token checks (e.g. for PubSub authenticated push subscription).
   120  		openIDCheck := auth.Authenticator{
   121  			Methods: []auth.Method{
   122  				&openid.GoogleIDTokenAuthMethod{
   123  					AudienceCheck: openid.AudienceMatchesHost,
   124  				},
   125  			},
   126  		}
   127  		mw := router.NewMiddlewareChain(openIDCheck.GetMiddleware())
   128  		srv.Routes.POST("/push", mw, func(c *router.Context) {
   129  			logging.Infof(c.Request.Context(), "Authenticated as %s", auth.CurrentIdentity(c.Request.Context()))
   130  			// TODO: check auth.CurrentIdentity(...) against an allowlist of allowed
   131  			// callers, etc.
   132  		})
   133  
   134  		// Using ID tokens for authenticating outbound calls. This synthetic example
   135  		// works on localhost only.
   136  		srv.Routes.GET("/call", mw, func(c *router.Context) {
   137  			tr, err := auth.GetRPCTransport(c.Request.Context(),
   138  				auth.AsSelf,
   139  				auth.WithIDTokenAudience("https://${host}"),
   140  			)
   141  			if err != nil {
   142  				http.Error(c.Writer, err.Error(), 500)
   143  				return
   144  			}
   145  
   146  			req, _ := http.NewRequest("POST", "http://127.0.0.1:8800/push", nil)
   147  			req.Host = "example.com"
   148  
   149  			resp, err := (&http.Client{Transport: tr}).Do(req)
   150  			if err != nil {
   151  				http.Error(c.Writer, err.Error(), 500)
   152  				return
   153  			}
   154  			defer resp.Body.Close()
   155  		})
   156  
   157  		// An example of a site that uses encrypted cookies for authentication.
   158  		templatesBundle := &templates.Bundle{
   159  			Loader:    templates.FileSystemLoader(os.DirFS("templates")),
   160  			DebugMode: func(context.Context) bool { return !srv.Options.Prod },
   161  			DefaultArgs: func(ctx context.Context, e *templates.Extra) (templates.Args, error) {
   162  				loginURL, err := auth.LoginURL(ctx, e.Request.URL.RequestURI())
   163  				if err != nil {
   164  					return nil, err
   165  				}
   166  				logoutURL, err := auth.LogoutURL(ctx, e.Request.URL.RequestURI())
   167  				if err != nil {
   168  					return nil, err
   169  				}
   170  				return templates.Args{
   171  					"IsAnonymous":            auth.CurrentIdentity(ctx) == identity.AnonymousIdentity,
   172  					"User":                   auth.CurrentUser(ctx),
   173  					"LoginURL":               loginURL,
   174  					"LogoutURL":              logoutURL,
   175  					"GoogleAnalyticsSnippet": analytics.Snippet(ctx),
   176  				}, nil
   177  			},
   178  		}
   179  		htmlPageMW := router.NewMiddlewareChain(
   180  			templates.WithTemplates(templatesBundle),
   181  			auth.Authenticate(srv.CookieAuth),
   182  		)
   183  
   184  		srv.Routes.GET("/", htmlPageMW, func(c *router.Context) {
   185  			templates.MustRender(c.Request.Context(), c.Writer, "pages/index.html", nil)
   186  		})
   187  		// To test redirects after login.
   188  		srv.Routes.GET("/test/*something", htmlPageMW, func(c *router.Context) {
   189  			templates.MustRender(c.Request.Context(), c.Writer, "pages/index.html", nil)
   190  		})
   191  
   192  		// Example of sending emails.
   193  		srv.Routes.GET("/send-mail", nil, func(c *router.Context) {
   194  			err := mailer.Send(c.Request.Context(), &mailer.Mail{
   195  				To:       []string{"someone@example.com"},
   196  				Subject:  "Hi",
   197  				TextBody: "How are you doing?",
   198  			})
   199  			if err != nil {
   200  				http.Error(c.Writer, err.Error(), 500)
   201  			}
   202  		})
   203  
   204  		return nil
   205  	})
   206  }
   207  
   208  type greeterServer struct{}
   209  
   210  func (*greeterServer) SayHi(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
   211  	logging.Infof(ctx, "Hi")
   212  	time.Sleep(100 * time.Millisecond)
   213  	return &emptypb.Empty{}, nil
   214  }