diff options
Diffstat (limited to 'idvoc-2025/CommentsEngine')
| -rw-r--r-- | idvoc-2025/CommentsEngine/.gitignore | 1 | ||||
| -rw-r--r-- | idvoc-2025/CommentsEngine/Dockerfile | 22 | ||||
| -rw-r--r-- | idvoc-2025/CommentsEngine/README.md | 5 | ||||
| -rw-r--r-- | idvoc-2025/CommentsEngine/go.mod | 22 | ||||
| -rw-r--r-- | idvoc-2025/CommentsEngine/go.sum | 34 | ||||
| -rw-r--r-- | idvoc-2025/CommentsEngine/main.go | 128 | ||||
| -rw-r--r-- | idvoc-2025/CommentsEngine/redisc/redis.go | 83 |
7 files changed, 295 insertions, 0 deletions
diff --git a/idvoc-2025/CommentsEngine/.gitignore b/idvoc-2025/CommentsEngine/.gitignore new file mode 100644 index 0000000..a18b076 --- /dev/null +++ b/idvoc-2025/CommentsEngine/.gitignore @@ -0,0 +1 @@ +CommentsEngine diff --git a/idvoc-2025/CommentsEngine/Dockerfile b/idvoc-2025/CommentsEngine/Dockerfile new file mode 100644 index 0000000..b57c112 --- /dev/null +++ b/idvoc-2025/CommentsEngine/Dockerfile @@ -0,0 +1,22 @@ +FROM reg.undercloud.cri.epita.fr/docker/golang:1.24.3-alpine + +LABEL org.opencontainers.image.authors="martial.simon@epita.fr" + +RUN apk --no-cache add curl bash + +WORKDIR /usr/local/app + +COPY go.mod go.sum . ./ + +# Setup an app user so the container doesn't run as the root user +RUN go install && adduser -D app +USER app + +EXPOSE 8000 + +ENV REDIS_ENDPOINT="redis:6379" +ENV HOST="0.0.0.0" +ENV PORT=8000 + +# Run the app when starting a container +CMD ["CommentsEngine"] diff --git a/idvoc-2025/CommentsEngine/README.md b/idvoc-2025/CommentsEngine/README.md new file mode 100644 index 0000000..4381739 --- /dev/null +++ b/idvoc-2025/CommentsEngine/README.md @@ -0,0 +1,5 @@ +## Env variables + +`REDIS_ENDPOINT`: The endpoint (host + port) on which redis can be contacted +`HOST`: The host on which to bind+listen to +`PORT`: The port on which to bind+listen to diff --git a/idvoc-2025/CommentsEngine/go.mod b/idvoc-2025/CommentsEngine/go.mod new file mode 100644 index 0000000..a2d1f24 --- /dev/null +++ b/idvoc-2025/CommentsEngine/go.mod @@ -0,0 +1,22 @@ +module CommentsEngine + +go 1.21.5 + +require ( + github.com/gorilla/handlers v1.5.2 + github.com/redis/go-redis/v9 v9.3.1 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/prometheus/client_golang v1.18.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + golang.org/x/sys v0.15.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect +) diff --git a/idvoc-2025/CommentsEngine/go.sum b/idvoc-2025/CommentsEngine/go.sum new file mode 100644 index 0000000..fd15fe6 --- /dev/null +++ b/idvoc-2025/CommentsEngine/go.sum @@ -0,0 +1,34 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/redis/go-redis/v9 v9.3.1 h1:KqdY8U+3X6z+iACvumCNxnoluToB+9Me+TvyFa21Mds= +github.com/redis/go-redis/v9 v9.3.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/idvoc-2025/CommentsEngine/main.go b/idvoc-2025/CommentsEngine/main.go new file mode 100644 index 0000000..9fb8105 --- /dev/null +++ b/idvoc-2025/CommentsEngine/main.go @@ -0,0 +1,128 @@ +package main + +import ( + "CommentsEngine/redisc" + "encoding/json" + "errors" + "fmt" + "github.com/gorilla/handlers" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/redis/go-redis/v9" + "io" + "log" + "net/http" + "os" +) + +func getenv(key, fallback string) string { + value := os.Getenv(key) + if len(value) == 0 { + return fallback + } + return value +} + +var ( + httpHits = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "comments_engine_http_hits_total", + Help: "The total number of hits on a given route", + }, []string{"route", "method"}) + commentsReceived = promauto.NewCounter(prometheus.CounterOpts{ + Name: "comments_engine_comments_received_total", + Help: "The total number of comments received", + }) + errorsSent = promauto.NewCounter(prometheus.CounterOpts{ + Name: "comments_interactor_errors_sent_total", + Help: "The total number of errors returned", + }) +) + +func getRoot(w http.ResponseWriter, r *http.Request) { + httpHits.With(prometheus.Labels{"route": "/", "method": r.Method}).Inc() + io.WriteString(w, "CommentsEngine answering here !\n") +} + +func sendError(w http.ResponseWriter, err error, additionalInfo string) { + errorsSent.Inc() + w.WriteHeader(http.StatusInternalServerError) + resp := make(map[string]string) + resp["message"] = fmt.Sprint(err) + resp["additionalInfo"] = additionalInfo + jsonResp, err := json.Marshal(resp) + if err != nil { + log.Fatalf("Error happened in JSON marshal. Err: %s", err) + } + w.Write(jsonResp) +} + +func sendMessage(w http.ResponseWriter, message string) { + commentsReceived.Inc() + resp := make(map[string]string) + resp["message"] = message + jsonResp, err := json.Marshal(resp) + if err != nil { + log.Fatalf("Error happened in JSON marshal. Err: %s", err) + } + w.Write(jsonResp) +} + +func getLatestComments(w http.ResponseWriter, r *http.Request) { + httpHits.With(prometheus.Labels{"route": "/latest", "method": r.Method}).Inc() + w.Header().Set("Content-Type", "application/json") + messages, err := redisc.GetLatestComments() + if err == nil { + io.WriteString(w, messages) + } else { + sendError(w, err, "Maybe there is a problem with redis backend ?") + } +} + +type Comment struct { + Comment string `json:"comment"` +} + +func sendComment(w http.ResponseWriter, r *http.Request) { + httpHits.With(prometheus.Labels{"route": "/comment", "method": r.Method}).Inc() + w.Header().Set("Content-Type", "application/json") + if r.Method != http.MethodPost { + sendError(w, errors.New("POST not used"), "HTTP method was not POST") + return + } + comment := &Comment{} + err := json.NewDecoder(r.Body).Decode(comment) + if err != nil { + sendError(w, err, "The data sent was likely incorrect") + return + } + _, err = redisc.StoreComment(comment.Comment) + if err != nil { + sendError(w, err, "Problem with redis maybe ?") + return + } + sendMessage(w, "stored !") +} + +func main() { + http.HandleFunc("/", getRoot) + http.HandleFunc("/latest", getLatestComments) + http.HandleFunc("/comment", sendComment) + http.Handle("/metrics", promhttp.Handler()) + + redisc.Client = redis.NewClient(&redis.Options{ + Addr: getenv("REDIS_ENDPOINT", "localhost:6379"), + Password: "", // no password set + DB: 0, // use default DB + }) + + listeningHostPort := getenv("HOST", "127.0.0.1") + ":" + getenv("PORT", "8000") + fmt.Printf("Starting the server on %s\n", listeningHostPort) + err := http.ListenAndServe(listeningHostPort, handlers.LoggingHandler(os.Stdout, http.DefaultServeMux)) + if errors.Is(err, http.ErrServerClosed) { + fmt.Printf("server closed\n") + } else if err != nil { + fmt.Printf("error starting server: %s\n", err) + os.Exit(1) + } +} diff --git a/idvoc-2025/CommentsEngine/redisc/redis.go b/idvoc-2025/CommentsEngine/redisc/redis.go new file mode 100644 index 0000000..88d9a08 --- /dev/null +++ b/idvoc-2025/CommentsEngine/redisc/redis.go @@ -0,0 +1,83 @@ +package redisc + +import ( + "context" + "encoding/json" + "github.com/redis/go-redis/v9" + "log" + "strconv" +) + +var Client *redis.Client + +func getIndex(ctx context.Context) (int, error) { + val, err := Client.Get(ctx, "index").Result() + var i int + if err != nil && err != redis.Nil { + log.Printf("Problem with redis: %s", err) + return -1, err + } else if err == redis.Nil { + i = 0 + } else { + i, err = strconv.Atoi(val) + if err != nil { + log.Printf("Problem in stored data: %s", err) + return -1, err + } + } + return i, nil +} + +func getIndexAndBump(ctx context.Context) (int, error) { + i, err := getIndex(ctx) + if err != nil { + return -1, err + } + err = Client.Set(ctx, "index", strconv.Itoa(i+1), 0).Err() + if err != nil { + log.Printf("Problem with redis while trying to store data: %s", err) + return -1, err + } + return i + 1, err +} + +func StoreComment(comment string) (int, error) { + ctx := context.Background() + i, err := getIndexAndBump(ctx) + if err != nil { + return -1, err + } + err = Client.Set(ctx, "comment"+strconv.Itoa(i), comment, 0).Err() + if err != nil { + log.Printf("Problem with redis while trying to store data: %s", err) + return -1, err + } + return i, err +} + +func GetLatestComments() (string, error) { + messages := make([]string, 0) + ctx := context.Background() + index, err := getIndex(ctx) + if err != nil { + return "", err + } + for i := 0; i < 10; i++ { + if index-i <= 0 { + break + } + val, err := Client.Get(ctx, "comment"+strconv.Itoa(index-i)).Result() + if err != nil && err != redis.Nil { + log.Printf("Problem with redis while trying to retrieve data: %s", err) + return "", err + } else if err == redis.Nil { + continue + } + messages = append(messages, val) + } + msg, err := json.Marshal(messages) + if err != nil { + log.Fatalf("Json issue") + } + return string(msg), err +} |
