diff --git a/kubernetes-manifests/cartservice.yaml b/kubernetes-manifests/cartservice.yaml index 7d3f564..a0d24f6 100644 --- a/kubernetes-manifests/cartservice.yaml +++ b/kubernetes-manifests/cartservice.yaml @@ -20,6 +20,10 @@ spec: value: "7070" - name: LISTEN_ADDR value: "0.0.0.0" + - name: GRPC_TRACE + value: "all" + - name: GRPC_VERBOSITY + value: "debug" resources: requests: cpu: 200m diff --git a/src/cartservice/Dockerfile b/src/cartservice/Dockerfile index 5440770..dd8993c 100644 --- a/src/cartservice/Dockerfile +++ b/src/cartservice/Dockerfile @@ -3,7 +3,7 @@ RUN apt update && apt install net-tools telnet WORKDIR /app COPY . . RUN dotnet restore && \ - dotnet build \ + dotnet build && \ dotnet publish WORKDIR /app/bin/Debug/netcoreapp2.0 ENTRYPOINT ["dotnet", "cartservice.dll", "start"] diff --git a/src/frontend/handlers.go b/src/frontend/handlers.go index 514f2aa..6bf45b0 100644 --- a/src/frontend/handlers.go +++ b/src/frontend/handlers.go @@ -12,6 +12,7 @@ import ( "github.com/google/uuid" "github.com/gorilla/mux" + "github.com/pkg/errors" pb "frontend/genproto" ) @@ -36,7 +37,7 @@ func ensureSessionID(next http.HandlerFunc) http.HandlerFunc { MaxAge: cookieMaxAge, }) } else if err != nil { - renderHTTPError(w, fmt.Errorf("unrecognized cookie error: %+v", err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrap(err, "unrecognized cookie error"), http.StatusInternalServerError) return } else { sessionID = c.Value @@ -51,17 +52,17 @@ func (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) { log.Printf("[home] session_id=%s currency=%s", sessionID(r), currentCurrency(r)) currencies, err := fe.getCurrencies(r.Context()) if err != nil { - renderHTTPError(w, fmt.Errorf("could not retrieve currencies: %+v", err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrap(err, "could not retrieve currencies"), http.StatusInternalServerError) return } products, err := fe.getProducts(r.Context()) if err != nil { - renderHTTPError(w, fmt.Errorf("could not retrieve products: %+v", err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrap(err, "could not retrieve products"), http.StatusInternalServerError) return } cart, err := fe.getCart(r.Context(), sessionID(r)) if err != nil { - renderHTTPError(w, fmt.Errorf("could not retrieve cart: %+v", err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrap(err, "could not retrieve cart"), http.StatusInternalServerError) return } @@ -73,7 +74,7 @@ func (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) { for i, p := range products { price, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r)) if err != nil { - renderHTTPError(w, fmt.Errorf("failed to do currency conversion for product %s: %+v", p.GetId(), err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrapf(err, "failed to do currency conversion for product %s", p.GetId()), http.StatusInternalServerError) return } ps[i] = productView{p, price} @@ -93,36 +94,36 @@ func (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) { func (fe *frontendServer) productHandler(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] if id == "" { - renderHTTPError(w, fmt.Errorf("product id not specified"), http.StatusBadRequest) + renderHTTPError(w, errors.New("product id not specified"), http.StatusBadRequest) return } log.Printf("[productHandler] id=%s currency=%s session=%s", id, currentCurrency(r), sessionID(r)) p, err := fe.getProduct(r.Context(), id) if err != nil { - renderHTTPError(w, fmt.Errorf("could not retrieve product: %+v", err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrap(err, "could not retrieve product"), http.StatusInternalServerError) return } currencies, err := fe.getCurrencies(r.Context()) if err != nil { - renderHTTPError(w, fmt.Errorf("could not retrieve currencies: %+v", err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrap(err, "could not retrieve currencies"), http.StatusInternalServerError) return } cart, err := fe.getCart(r.Context(), sessionID(r)) if err != nil { - renderHTTPError(w, fmt.Errorf("could not retrieve cart: %+v", err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrap(err, "could not retrieve cart"), http.StatusInternalServerError) return } price, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r)) if err != nil { - renderHTTPError(w, fmt.Errorf("failed to convert currency: %+v", err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrap(err, "failed to convert currency"), http.StatusInternalServerError) return } recommendations, err := fe.getRecommendations(r.Context(), sessionID(r), []string{id}) if err != nil { - renderHTTPError(w, fmt.Errorf("failed to get product recommendations: %+v", err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrap(err, "failed to get product recommendations"), http.StatusInternalServerError) return } @@ -147,19 +148,19 @@ func (fe *frontendServer) addToCartHandler(w http.ResponseWriter, r *http.Reques quantity, _ := strconv.ParseUint(r.FormValue("quantity"), 10, 32) productID := r.FormValue("product_id") if productID == "" || quantity == 0 { - renderHTTPError(w, fmt.Errorf("invalid form input"), http.StatusBadRequest) + renderHTTPError(w, errors.New("invalid form input"), http.StatusBadRequest) return } log.Printf("[addToCart] product_id=%s qty=%d session_id=%s", productID, quantity, sessionID(r)) p, err := fe.getProduct(r.Context(), productID) if err != nil { - renderHTTPError(w, fmt.Errorf("could not retrieve product: %+v", err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrap(err, "could not retrieve product"), http.StatusInternalServerError) return } if err := fe.insertCart(r.Context(), sessionID(r), p.GetId(), int32(quantity)); err != nil { - renderHTTPError(w, fmt.Errorf("failed to add to cart: %+v", err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrap(err, "failed to add to cart"), http.StatusInternalServerError) return } w.Header().Set("location", "/cart") @@ -170,7 +171,7 @@ func (fe *frontendServer) emptyCartHandler(w http.ResponseWriter, r *http.Reques log.Printf("[emptyCart] session_id=%s", sessionID(r)) if err := fe.emptyCart(r.Context(), sessionID(r)); err != nil { - renderHTTPError(w, fmt.Errorf("failed to empty cart: %+v", err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrap(err, "failed to empty cart"), http.StatusInternalServerError) return } w.Header().Set("location", "/") @@ -181,24 +182,24 @@ func (fe *frontendServer) viewCartHandler(w http.ResponseWriter, r *http.Request log.Printf("[viewCart] session_id=%s", sessionID(r)) currencies, err := fe.getCurrencies(r.Context()) if err != nil { - renderHTTPError(w, fmt.Errorf("could not retrieve currencies: %+v", err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrap(err, "could not retrieve currencies"), http.StatusInternalServerError) return } cart, err := fe.getCart(r.Context(), sessionID(r)) if err != nil { - renderHTTPError(w, fmt.Errorf("could not retrieve cart: %+v", err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrap(err, "could not retrieve cart"), http.StatusInternalServerError) return } recommendations, err := fe.getRecommendations(r.Context(), sessionID(r), cartIDs(cart)) if err != nil { - renderHTTPError(w, fmt.Errorf("failed to get product recommendations: %+v", err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrap(err, "failed to get product recommendations"), http.StatusInternalServerError) return } shippingCost, err := fe.getShippingQuote(r.Context(), cart, currentCurrency(r)) if err != nil { - renderHTTPError(w, fmt.Errorf("failed to get shipping quote: %+v", err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrap(err, "failed to get shipping quote"), http.StatusInternalServerError) return } @@ -212,12 +213,12 @@ func (fe *frontendServer) viewCartHandler(w http.ResponseWriter, r *http.Request for i, item := range cart { p, err := fe.getProduct(r.Context(), item.GetProductId()) if err != nil { - renderHTTPError(w, fmt.Errorf("could not retrieve product #%s: %+v", item.GetProductId(), err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrapf(err, "could not retrieve product #%s", item.GetProductId()), http.StatusInternalServerError) return } price, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r)) if err != nil { - renderHTTPError(w, fmt.Errorf("could not convert currency for product #%s: %+v", item.GetProductId(), err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrapf(err, "could not convert currency for product #%s", item.GetProductId()), http.StatusInternalServerError) return } @@ -280,7 +281,7 @@ func (fe *frontendServer) placeOrderHandler(w http.ResponseWriter, r *http.Reque Country: country}, }) if err != nil { - renderHTTPError(w, fmt.Errorf("failed to complete the order: %+v", err), http.StatusInternalServerError) + renderHTTPError(w, errors.Wrap(err, "failed to complete the order"), http.StatusInternalServerError) return } log.Printf("order #%s completed", order.GetOrder().GetOrderId()) @@ -334,10 +335,12 @@ func (fe *frontendServer) setCurrencyHandler(w http.ResponseWriter, r *http.Requ } func renderHTTPError(w http.ResponseWriter, err error, code int) { - log.Printf("[error] %+v (code=%d)", err, code) + log.Printf("[error](code=%d) %+v ", code, err) + errMsg := fmt.Sprintf("%+v", err) + w.WriteHeader(code) templates.ExecuteTemplate(w, "error", map[string]interface{}{ - "error": err.Error(), + "error": errMsg, "status_code": code, "status": http.StatusText(code)}) } diff --git a/src/frontend/main.go b/src/frontend/main.go index f14f755..dcbe399 100644 --- a/src/frontend/main.go +++ b/src/frontend/main.go @@ -6,6 +6,9 @@ import ( "log" "net/http" "os" + "time" + + "github.com/pkg/errors" "github.com/gorilla/mux" "google.golang.org/grpc" @@ -56,6 +59,7 @@ func main() { 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") @@ -64,31 +68,12 @@ func main() { mustMapEnv(&svc.checkoutSvcAddr, "CHECKOUT_SERVICE_ADDR") mustMapEnv(&svc.shippingSvcAddr, "SHIPPING_SERVICE_ADDR") - var err error - svc.currencySvcConn, err = grpc.DialContext(ctx, svc.currencySvcAddr, grpc.WithInsecure()) - if err != nil { - log.Fatalf("failed to connect currency service: %+v", err) - } - svc.productCatalogSvcConn, err = grpc.DialContext(ctx, svc.productCatalogSvcAddr, grpc.WithInsecure()) - if err != nil { - log.Fatalf("failed to connect productcatalog service: %+v", err) - } - svc.cartSvcConn, err = grpc.DialContext(ctx, svc.cartSvcAddr, grpc.WithInsecure()) - if err != nil { - log.Fatalf("failed to connect cart service at %s: %+v", svc.cartSvcAddr, err) - } - svc.recommendationSvcConn, err = grpc.DialContext(ctx, svc.recommendationSvcAddr, grpc.WithInsecure()) - if err != nil { - log.Fatalf("failed to connect recommendation service at %s: %+v", svc.recommendationSvcAddr, err) - } - svc.shippingSvcConn, err = grpc.DialContext(ctx, svc.shippingSvcAddr, grpc.WithInsecure()) - if err != nil { - log.Fatalf("failed to connect shipping service at %s: %+v", svc.shippingSvcAddr, err) - } - svc.checkoutSvcConn, err = grpc.DialContext(ctx, svc.checkoutSvcAddr, grpc.WithInsecure()) - if err != nil { - log.Fatalf("failed to connect checkout service at %s: %+v", svc.checkoutSvcAddr, err) - } + 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) r := mux.NewRouter() r.HandleFunc("/", ensureSessionID(svc.homeHandler)).Methods(http.MethodGet, http.MethodHead) @@ -100,8 +85,8 @@ func main() { r.HandleFunc("/logout", svc.logoutHandler).Methods(http.MethodGet) r.HandleFunc("/cart/checkout", ensureSessionID(svc.placeOrderHandler)).Methods(http.MethodPost) r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/")))) - log.Printf("starting server on :" + srvPort) - log.Fatal(http.ListenAndServe(":"+srvPort, r)) + log.Printf("starting server on " + addr + ":" + srvPort) + log.Fatal(http.ListenAndServe(addr+":"+srvPort, r)) } func mustMapEnv(target *string, envKey string) { @@ -111,3 +96,11 @@ func mustMapEnv(target *string, envKey string) { } *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)) + if err != nil { + panic(errors.Wrapf(err, "grpc: failed to connect %s", addr)) + } +} diff --git a/src/frontend/rpc.go b/src/frontend/rpc.go index 00657f3..1008241 100644 --- a/src/frontend/rpc.go +++ b/src/frontend/rpc.go @@ -2,13 +2,10 @@ package main import ( "context" - "fmt" - - "google.golang.org/grpc/codes" pb "frontend/genproto" - "google.golang.org/grpc/status" + "github.com/pkg/errors" ) const ( @@ -44,10 +41,6 @@ func (fe *frontendServer) getProduct(ctx context.Context, id string) (*pb.Produc func (fe *frontendServer) getCart(ctx context.Context, userID string) ([]*pb.CartItem, error) { resp, err := pb.NewCartServiceClient(fe.cartSvcConn).GetCart(ctx, &pb.GetCartRequest{UserId: userID}) - if status.Code(err) == codes.Canceled { - // TODO(ahmetb) remove this workaround when cartservice returns ok response to GetCart() with non-existing users - return nil, nil - } return resp.GetItems(), err } @@ -85,10 +78,7 @@ func (fe *frontendServer) getShippingQuote(ctx context.Context, items []*pb.Cart return nil, err } localized, err := fe.convertCurrency(ctx, quote.GetCostUsd(), currency) - if err != nil { - return nil, fmt.Errorf("failed to convert currency for shipping cost: %+v", err) - } - return localized, nil + return localized, errors.Wrap(err, "failed to convert currency for shipping cost") } func (fe *frontendServer) getRecommendations(ctx context.Context, userID string, productIDs []string) ([]*pb.Product, error) { @@ -101,7 +91,7 @@ func (fe *frontendServer) getRecommendations(ctx context.Context, userID string, for i, v := range resp.GetProductIds() { p, err := fe.getProduct(ctx, v) if err != nil { - return nil, fmt.Errorf("failed to get recommended product info (#%s): %+v", v, err) + return nil, errors.Wrapf(err, "failed to get recommended product info (#%s)", v) } out[i] = p } diff --git a/src/frontend/templates/error.html b/src/frontend/templates/error.html index 24e167a..ce8baaa 100644 --- a/src/frontend/templates/error.html +++ b/src/frontend/templates/error.html @@ -8,7 +8,7 @@
Something has failed. Below are some details for debugging.
HTTP Status: {{.status_code}} {{.status}}
-{{- .error -}}