Merge remote-tracking branch 'origin' into createCartService

This commit is contained in:
Simon Zeltser 2018-06-25 09:49:39 -07:00
commit ef92ddc351
7 changed files with 2649 additions and 0 deletions

6
src/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
# block vgo cache dir
v/
# temp gopath
github.com
golang.org
google.golang.org

6
src/frontend/genproto.sh Executable file
View file

@ -0,0 +1,6 @@
#!/bin/bash -e
PATH=$PATH:$GOPATH/bin
protodir=../../pb
protoc --go_out=plugins=grpc:genproto -I $protodir $protodir/demo.proto

File diff suppressed because it is too large Load diff

109
src/frontend/handlers.go Normal file
View file

@ -0,0 +1,109 @@
package main
import (
"context"
"html/template"
"log"
"net/http"
"github.com/google/uuid"
)
var (
homeTemplate = template.Must(template.ParseFiles("templates/home.html"))
)
func refreshCookies(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
for _, c := range r.Cookies() {
c.MaxAge = cookieMaxAge
http.SetCookie(w, c)
}
next(w, r)
}
}
func ensureSessionID(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var sessionID string
c, err := r.Cookie(cookieSessionID)
if err == http.ErrNoCookie {
u, _ := uuid.NewRandom()
sessionID = u.String()
http.SetCookie(w, &http.Cookie{
Name: cookieSessionID,
Value: sessionID,
MaxAge: cookieMaxAge,
})
} else if err != nil {
log.Printf("unrecognized cookie error: %+v", err)
w.WriteHeader(http.StatusInternalServerError)
return
} else {
sessionID = c.Value
}
ctx := context.WithValue(r.Context(), ctxKeySessionID{}, sessionID)
r = r.WithContext(ctx)
next(w, r)
}
}
func (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("[home] session_id=%+v", r.Context().Value(ctxKeySessionID{}))
currencies, err := fe.getCurrencies(r.Context())
if err != nil {
log.Println(err) // TODO(ahmetb) use structured logging
w.WriteHeader(http.StatusInternalServerError)
return
}
log.Printf("currencies: %+v", currencies)
products, err := fe.getProducts(r.Context())
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
log.Printf("# products: %d", len(products))
if err := homeTemplate.Execute(w, map[string]interface{}{
"user_currency": currentCurrency(r),
"currencies": currencies,
"products": products,
}); err != nil {
log.Println(err)
}
}
func (fe *frontendServer) logoutHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("[home] session_id=%+v", r.Context().Value(ctxKeySessionID{}))
// clear all cookies
for _, c := range r.Cookies() {
c.MaxAge = -1
http.SetCookie(w, c)
}
w.Header().Set("Location", "/")
w.WriteHeader(http.StatusFound)
}
func (fe *frontendServer) setCurrencyHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("[setCurrency] session_id=%+v", r.Context().Value(ctxKeySessionID{}))
cur := r.FormValue("currency_code")
if cur != "" {
http.SetCookie(w, &http.Cookie{
Name: cookieCurrency,
Value: cur,
MaxAge: cookieMaxAge,
})
}
w.Header().Set("Location", "/")
w.WriteHeader(http.StatusFound)
}
func currentCurrency(r *http.Request) string {
c, _ := r.Cookie(cookieCurrency)
if c != nil {
return c.Value
}
return defaultCurrency
}

84
src/frontend/main.go Normal file
View file

@ -0,0 +1,84 @@
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"github.com/gorilla/mux"
"google.golang.org/grpc"
)
const (
port = "8080"
defaultCurrency = "USD"
cookieMaxAge = 60 * 60 * 48
cookieSessionID = "session-id"
cookieCurrency = "currency"
)
var (
whitelistedCurrencies = map[string]bool{
"USD": true, "EUR": true, "CAD": true, "JPY": true}
)
type ctxKeySessionID struct{}
type frontendServer struct {
productCatalogSvcAddr string
productCatalogSvcConn *grpc.ClientConn
currencySvcAddr string
currencySvcConn *grpc.ClientConn
cartSvcAddr string
cartSvcConn *grpc.ClientConn
}
func main() {
ctx := context.Background()
srvPort := port
if os.Getenv("PORT") != "" {
srvPort = os.Getenv("PORT")
}
svc := new(frontendServer)
mustMapEnv(&svc.productCatalogSvcAddr, "PRODUCT_CATALOG_SERVICE_ADDR")
mustMapEnv(&svc.currencySvcAddr, "CURRENCY_SERVICE_ADDR")
// mustMapEnv(&svc.cartSvcAddr, "CART_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)
}
r := mux.NewRouter()
r.HandleFunc("/", refreshCookies(
ensureSessionID(
svc.homeHandler))).
Methods(http.MethodGet)
r.HandleFunc("/logout", svc.logoutHandler).
Methods(http.MethodGet)
r.HandleFunc("/setCurrency", refreshCookies(
ensureSessionID(
svc.setCurrencyHandler))).
Methods(http.MethodPost)
log.Printf("starting server on :" + srvPort)
log.Fatal(http.ListenAndServe("localhost:"+srvPort, r))
}
func mustMapEnv(target *string, envKey string) {
v := os.Getenv(envKey)
if v == "" {
panic(fmt.Sprintf("environment variable %q not set", envKey))
}
*target = v
}

28
src/frontend/rpc.go Normal file
View file

@ -0,0 +1,28 @@
package main
import (
"context"
pb "frontend/genproto"
)
func (fe *frontendServer) getCurrencies(ctx context.Context) ([]string, error) {
currs, err := pb.NewCurrencyServiceClient(fe.currencySvcConn).
GetSupportedCurrencies(ctx, &pb.Empty{})
if err != nil {
return nil, err
}
var out []string
for _, c := range currs.CurrencyCodes {
if _, ok := whitelistedCurrencies[c]; ok {
out = append(out, c)
}
}
return out, nil
}
func (fe *frontendServer) getProducts(ctx context.Context) ([]*pb.Product, error) {
resp, err := pb.NewProductCatalogServiceClient(fe.productCatalogSvcConn).
ListProducts(ctx, &pb.Empty{})
return resp.GetProducts(), err
}

View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hipster Shop</title>
</head>
<body>
<form method="post" action="/setCurrency" id="currencySelector">
<select name="currency_code"
onchange="document.getElementById('currencySelector').submit()">
{{ range $.currencies }}
<option {{ if (eq . $.user_currency)}}selected="selected"{{end}}>{{.}}</option>
{{ end}}
</select>
</form>
<p>{{ len $.products }} products</p>
</body>
</html>