package main import ( "encoding/json" "flag" "log" "net/http" "os" "strconv" "sync" ) type visitsStore struct { m map[string]int *sync.RWMutex } func (s *visitsStore) visit(ending string) int { s.Lock() defer s.Unlock() oldVisits, has := s.m[ending] var newVisits int if has { newVisits = oldVisits + 1 } else { newVisits = 1 } s.m[ending] = newVisits return newVisits } type visitsHandler struct { store *visitsStore } func (h *visitsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // CORS: this is expected to be called from a different domain, // hoops require jumping w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Max-Age", "86400") w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") w.Header().Set("content-type", "application/json") if r.Method == http.MethodPost { var s string if err := json.NewDecoder(r.Body).Decode(&s); err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("cannot parse data")) return } w.WriteHeader(http.StatusOK) w.Write(([]byte)(strconv.Itoa(h.store.visit(s)))) return } if r.Method == http.MethodGet { jsonBytes, err := json.Marshal(h.store.m) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("cannot serialize")) return } w.WriteHeader(http.StatusOK) w.Write(jsonBytes) return } if r.Method == http.MethodOptions { w.WriteHeader(http.StatusOK) return } w.Header().Set("Allow", "POST, GET, OPTIONS") w.WriteHeader(http.StatusMethodNotAllowed) } func main() { addr := flag.String("addr", ":11568", "Listen address") initialFile := flag.String("file", "visits.json", "File with initial data in JSON format") flag.Parse() initialVisits := make(map[string]int) initialVisitsJson, err := os.ReadFile(*initialFile) if err == nil { json.Unmarshal(initialVisitsJson, &initialVisits) } else { log.Println("initial file", *initialFile, "could not be read, defaulting to empty data") } store := visitsStore{m: initialVisits, RWMutex: &sync.RWMutex{}} visitsH := &visitsHandler{store: &store} http.Handle("/visits", visitsH) log.Println("Starting web server...") http.ListenAndServe(*addr, nil) }