// Copyright 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "fmt" "net/http" "os" "time" "github.com/gorilla/mux" "github.com/pkg/errors" "github.com/sirupsen/logrus" "go.opentelemetry.io/otel/api/global" "go.opentelemetry.io/otel/api/kv" "go.opentelemetry.io/otel/exporters/trace/jaeger" "go.opentelemetry.io/otel/instrumentation/grpctrace" "google.golang.org/grpc" otelhttp "go.opentelemetry.io/contrib/instrumentation/net/http" sdktrace "go.opentelemetry.io/otel/sdk/trace" ) const ( port = "8080" defaultCurrency = "USD" cookieMaxAge = 60 * 60 * 48 cookiePrefix = "shop_" cookieSessionID = cookiePrefix + "session-id" cookieCurrency = cookiePrefix + "currency" ) var ( whitelistedCurrencies = map[string]bool{ "USD": true, "EUR": true, "CAD": true, "JPY": true, "GBP": true, "TRY": true} ) type ctxKeySessionID struct{} type frontendServer struct { productCatalogSvcAddr string productCatalogSvcConn *grpc.ClientConn currencySvcAddr string currencySvcConn *grpc.ClientConn cartSvcAddr string cartSvcConn *grpc.ClientConn recommendationSvcAddr string recommendationSvcConn *grpc.ClientConn checkoutSvcAddr string checkoutSvcConn *grpc.ClientConn shippingSvcAddr string shippingSvcConn *grpc.ClientConn adSvcAddr string adSvcConn *grpc.ClientConn } func main() { ctx := context.Background() log := logrus.New() log.Level = logrus.DebugLevel log.Formatter = &logrus.JSONFormatter{ FieldMap: logrus.FieldMap{ logrus.FieldKeyTime: "timestamp", logrus.FieldKeyLevel: "severity", logrus.FieldKeyMsg: "message", }, TimestampFormat: time.RFC3339Nano, } log.Out = os.Stdout initTracer(log) srvPort := port if os.Getenv("PORT") != "" { srvPort = os.Getenv("PORT") } addr := os.Getenv("LISTEN_ADDR") svc := new(frontendServer) mustMapEnv(&svc.productCatalogSvcAddr, "PRODUCT_CATALOG_SERVICE_ADDR") mustMapEnv(&svc.currencySvcAddr, "CURRENCY_SERVICE_ADDR") // svc.cartSvcAddr = "localhost:7070" mustMapEnv(&svc.cartSvcAddr, "CART_SERVICE_ADDR") mustMapEnv(&svc.recommendationSvcAddr, "RECOMMENDATION_SERVICE_ADDR") mustMapEnv(&svc.checkoutSvcAddr, "CHECKOUT_SERVICE_ADDR") mustMapEnv(&svc.shippingSvcAddr, "SHIPPING_SERVICE_ADDR") mustMapEnv(&svc.adSvcAddr, "AD_SERVICE_ADDR") mustConnGRPC(ctx, &svc.currencySvcConn, svc.currencySvcAddr) mustConnGRPC(ctx, &svc.productCatalogSvcConn, svc.productCatalogSvcAddr) mustConnGRPC(ctx, &svc.cartSvcConn, svc.cartSvcAddr) mustConnGRPC(ctx, &svc.recommendationSvcConn, svc.recommendationSvcAddr) mustConnGRPC(ctx, &svc.shippingSvcConn, svc.shippingSvcAddr) mustConnGRPC(ctx, &svc.checkoutSvcConn, svc.checkoutSvcAddr) mustConnGRPC(ctx, &svc.adSvcConn, svc.adSvcAddr) r := mux.NewRouter() r.HandleFunc("/", svc.homeHandler).Methods(http.MethodGet, http.MethodHead) r.HandleFunc("/product/{id}", svc.productHandler).Methods(http.MethodGet, http.MethodHead) r.HandleFunc("/cart", svc.viewCartHandler).Methods(http.MethodGet, http.MethodHead) r.HandleFunc("/cart", svc.addToCartHandler).Methods(http.MethodPost) r.HandleFunc("/cart/empty", svc.emptyCartHandler).Methods(http.MethodPost) r.HandleFunc("/setCurrency", svc.setCurrencyHandler).Methods(http.MethodPost) r.HandleFunc("/logout", svc.logoutHandler).Methods(http.MethodGet) r.HandleFunc("/cart/checkout", svc.placeOrderHandler).Methods(http.MethodPost) r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/")))) r.HandleFunc("/robots.txt", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, "User-agent: *\nDisallow: /") }) r.HandleFunc("/_healthz", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, "ok") }) var handler http.Handler = r handler = &logHandler{log: log, next: handler} // add logging handler = ensureSessionID(handler) // add session ID handler = otelhttp.NewHandler(handler, "hipstershop.Frontend/Recv.") log.Infof("starting server on " + addr + ":" + srvPort) log.Fatal(http.ListenAndServe(addr+":"+srvPort, handler)) } // initTracer creates a new trace provider instance and registers it as global trace provider. func initTracer(log logrus.FieldLogger) func() { svcAddr := os.Getenv("JAEGER_SERVICE_ADDR") // svcAddr := "http://localhost:14268/api/traces" podIp := os.Getenv("POD_IP") podName := os.Getenv("POD_NAME") nodeName := os.Getenv("NODE_NAME") serviceName := os.Getenv("SERVICE_NAME") if svcAddr == "" { log.Info("jaeger initialization disabled.") } endPoint := fmt.Sprintf("http://%s", svcAddr) // Create and install Jaeger export pipeline flush, err := jaeger.InstallNewPipeline( jaeger.WithCollectorEndpoint(endPoint), jaeger.WithProcess(jaeger.Process{ ServiceName: serviceName, Tags: []kv.KeyValue{ kv.String("exporter", "jaeger"), kv.Float64("float", 312.23), kv.String("ip", podIp), kv.String("name", podName), kv.String("node_name", nodeName), }, }), jaeger.WithSDK(&sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}), ) if err != nil { log.Fatal(err) } log.Info("jaeger initialization completed.") return func() { flush() } } func mustMapEnv(target *string, envKey string) { v := os.Getenv(envKey) if v == "" { panic(fmt.Sprintf("environment variable %q not set", envKey)) } *target = v } func mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) { var err error *conn, err = grpc.DialContext(ctx, addr, grpc.WithInsecure(), grpc.WithTimeout(time.Second*3), grpc.WithUnaryInterceptor(grpctrace.UnaryClientInterceptor(global.Tracer(""))), grpc.WithStreamInterceptor(grpctrace.StreamClientInterceptor(global.Tracer(""))), ) if err != nil { panic(errors.Wrapf(err, "grpc: failed to connect %s", addr)) } }