package main import ( "encoding/json" "fmt" "io/ioutil" "log" "math" "net/http" "os" "strings" "sync" "github.com/aws/aws-xray-sdk-go/xray" "github.com/pkg/errors" ) const defaultPort = "8080" const defaultStage = "default" const maxColors = 1000 var colors [maxColors]string var colorsIdx int var colorsMutext = &sync.Mutex{} func getServerPort() string { port := os.Getenv("PORT") if port != "" { return port } return defaultPort } func getStage() string { stage := os.Getenv("STAGE") if stage != "" { return stage } return defaultStage } func getXRAYAppName() string { appName := os.Getenv("XRAY_APP_NAME") if appName != "" { return appName } return "front" } func getColorTellerEndpoint() (string, error) { colorTellerEndpoint := os.Getenv("COLOR_HOST") if colorTellerEndpoint == "" { return "", errors.New("COLOR_HOST is not set") } return colorTellerEndpoint, nil } type colorHandler struct{} func (h *colorHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { color, err := getColorFromColorTeller(request) if err != nil { writer.WriteHeader(http.StatusInternalServerError) writer.Write([]byte("500 - Unexpected Error")) return } colorsMutext.Lock() defer colorsMutext.Unlock() addColor(color) statsJSON, err := json.Marshal(getRatios()) if err != nil { fmt.Fprintf(writer, `{"color":"%s", "error":"%s"}`, color, err) return } fmt.Fprintf(writer, `{"color":"%s", "stats": %s}`, color, statsJSON) } func addColor(color string) { colors[colorsIdx] = color colorsIdx++ if colorsIdx >= maxColors { colorsIdx = 0 } } func getRatios() map[string]float64 { counts := make(map[string]int) var total = 0 for _, c := range colors { if c != "" { counts[c]++ total++ } } ratios := make(map[string]float64) for k, v := range counts { ratio := float64(v) / float64(total) ratios[k] = math.Round(ratio*100) / 100 } return ratios } type clearColorStatsHandler struct{} func (h *clearColorStatsHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { colorsMutext.Lock() defer colorsMutext.Unlock() colorsIdx = 0 for i := range colors { colors[i] = "" } fmt.Fprint(writer, "cleared") } func getColorFromColorTeller(request *http.Request) (string, error) { colorTellerEndpoint, err := getColorTellerEndpoint() if err != nil { return "-n/a-", err } client := xray.Client(&http.Client{}) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s", colorTellerEndpoint), nil) if err != nil { return "-n/a-", err } resp, err := client.Do(req.WithContext(request.Context())) if err != nil { return "-n/a-", err } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return "-n/a-", err } color := strings.TrimSpace(string(body)) if len(color) < 1 { return "-n/a-", errors.New("Empty response from colorTeller") } return color, nil } type pingHandler struct{} func (h *pingHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { log.Println("ping requested, reponding with HTTP 200") writer.WriteHeader(http.StatusOK) } func main() { log.Println("Starting server, listening on port " + getServerPort()) colorTellerEndpoint, err := getColorTellerEndpoint() if err != nil { log.Fatalln(err) } log.Println("Using color-teller at " + colorTellerEndpoint) xraySegmentNamer := xray.NewFixedSegmentNamer(getXRAYAppName()) http.Handle("/color", xray.Handler(xraySegmentNamer, &colorHandler{})) http.Handle("/color/clear", xray.Handler(xraySegmentNamer, &clearColorStatsHandler{})) http.Handle("/ping", xray.Handler(xraySegmentNamer, &pingHandler{})) log.Fatal(http.ListenAndServe(":"+getServerPort(), nil)) }