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 }