diff options
| author | Martial Simon <msimon_fr@hotmail.com> | 2025-09-15 01:07:58 +0200 |
|---|---|---|
| committer | Martial Simon <msimon_fr@hotmail.com> | 2025-09-15 01:07:58 +0200 |
| commit | 967be9e750221ab2ab783f95df79bb26d290a45e (patch) | |
| tree | 6802900a5e975f9f68b169f0f503f040056d6952 /idvoc-2025/CommentsInteractor | |
Diffstat (limited to 'idvoc-2025/CommentsInteractor')
| -rw-r--r-- | idvoc-2025/CommentsInteractor/.gitignore | 1 | ||||
| -rw-r--r-- | idvoc-2025/CommentsInteractor/Dockerfile | 24 | ||||
| -rw-r--r-- | idvoc-2025/CommentsInteractor/README.md | 5 | ||||
| -rw-r--r-- | idvoc-2025/CommentsInteractor/go.mod | 18 | ||||
| -rw-r--r-- | idvoc-2025/CommentsInteractor/go.sum | 26 | ||||
| -rw-r--r-- | idvoc-2025/CommentsInteractor/main.go | 147 |
6 files changed, 221 insertions, 0 deletions
diff --git a/idvoc-2025/CommentsInteractor/.gitignore b/idvoc-2025/CommentsInteractor/.gitignore new file mode 100644 index 0000000..f832480 --- /dev/null +++ b/idvoc-2025/CommentsInteractor/.gitignore @@ -0,0 +1 @@ +CommentsInteractor diff --git a/idvoc-2025/CommentsInteractor/Dockerfile b/idvoc-2025/CommentsInteractor/Dockerfile new file mode 100644 index 0000000..15f9412 --- /dev/null +++ b/idvoc-2025/CommentsInteractor/Dockerfile @@ -0,0 +1,24 @@ +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 main.go ./ + +RUN go install + +# Setup an app user so the container doesn't run as the root user +RUN adduser -D app +USER app + +EXPOSE 9000 + +ENV COMMENTS_ENGINE_ENDPOINT="commentsengine:8000" +ENV HOST="0.0.0.0" +ENV PORT=9000 + +# Run the app when starting a container +CMD ["CommentsInteractor"] diff --git a/idvoc-2025/CommentsInteractor/README.md b/idvoc-2025/CommentsInteractor/README.md new file mode 100644 index 0000000..0d896bc --- /dev/null +++ b/idvoc-2025/CommentsInteractor/README.md @@ -0,0 +1,5 @@ +## Env variables + +`COMMENTS_ENGINE_ENDPOINT`: The endpoint (host + port) where to find CommentsEngine +`HOST`: The host on which to bind+listen to +`PORT`: The port on which to bind+listen to diff --git a/idvoc-2025/CommentsInteractor/go.mod b/idvoc-2025/CommentsInteractor/go.mod new file mode 100644 index 0000000..dcebe1f --- /dev/null +++ b/idvoc-2025/CommentsInteractor/go.mod @@ -0,0 +1,18 @@ +module CommentsInteractor + +go 1.21.5 + +require github.com/gorilla/handlers v1.5.2 + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // 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/CommentsInteractor/go.sum b/idvoc-2025/CommentsInteractor/go.sum new file mode 100644 index 0000000..33b6970 --- /dev/null +++ b/idvoc-2025/CommentsInteractor/go.sum @@ -0,0 +1,26 @@ +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/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +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= +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/CommentsInteractor/main.go b/idvoc-2025/CommentsInteractor/main.go new file mode 100644 index 0000000..9a19243 --- /dev/null +++ b/idvoc-2025/CommentsInteractor/main.go @@ -0,0 +1,147 @@ +package main + +import ( + "bytes" + "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" + "io" + "log" + "net/http" + "os" + "strconv" +) + +func getenv(key, fallback string) string { + value := os.Getenv(key) + if len(value) == 0 { + return fallback + } + return value +} + +type Comment struct { + Comment string `json:"comment"` +} + +type EngineError struct { + Message string `json:"message"` + AdditionalInfo string `json:"additionalInfo"` +} + +var comments_engine_endpoint string +var ( + httpHits = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "comments_interactor_http_hits_total", + Help: "The total number of hits on a given route", + }, []string{"route", "method"}) + commentsReceived = promauto.NewCounter(prometheus.CounterOpts{ + Name: "comments_interactor_comments_received_total", + Help: "The total number of comments received (successfully posted or not)", + }) + commentsPosted = promauto.NewCounter(prometheus.CounterOpts{ + Name: "comments_interactor_comments_posted_total", + Help: "The total number of comments posted (successfully posted)", + }) +) + +func sendComment(w http.ResponseWriter, r *http.Request) { + commentsReceived.Inc() + if err := r.ParseForm(); err != nil { + fmt.Fprintf(w, "<h1>error</h1><p>ParseForm() err: %v</p>", err) + return + } + var comment Comment + comment.Comment = r.FormValue("comment") + if comment.Comment == "" { + fmt.Fprintf(w, "<h1>error</h1><p>Empty comment or malformed</p>") + return + } + obj, err := json.Marshal(comment) + if err != nil { + fmt.Fprintf(w, "<h1>error</h1><p>Empty comment or malformed</p>") + return + } + _, err = http.Post("http://"+comments_engine_endpoint+"/comment", "application/json", bytes.NewReader(obj)) + if err == nil { + fmt.Fprintf(w, "<h1>Success !</h1><p>Comment posted</p>") + commentsPosted.Inc() + } else { + fmt.Fprintf(w, "<h1>Error</h1><p>Error while trying to send a comment: %s", err) + } +} + +func showDashboard(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, "<h1>Latest comments</h1>") + io.WriteString(w, "<p>") + resp, err := http.Get("http://" + comments_engine_endpoint + "/latest") + if err != nil { + log.Printf("Could not HTTP get on CommentsEngine: %s", err) + io.WriteString(w, fmt.Sprintf("Error contacting the backend, %s", err)) + } else { + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + var comments []string + err = json.Unmarshal(body, &comments) + if err != nil { + var engineError EngineError + err = json.Unmarshal(body, &engineError) + if err != nil { + log.Printf("CommentsEngine returned unexpected value: %s", err) + io.WriteString(w, fmt.Sprintf("CommentsEngine returned badly formated values, %s", err)) + } else { + io.WriteString(w, fmt.Sprintf("CommentsEngine returned an error: %s<br/>Additional info: %s", engineError.Message, engineError.AdditionalInfo)) + } + } else { + for i, comment := range comments { + io.WriteString(w, "<div><h3>comment "+strconv.Itoa(i)+"</h3><p>"+comment+"</p></div>") + } + } + } + io.WriteString(w, "</p>") + io.WriteString(w, "<h1>Send comment</h1>") + io.WriteString(w, "<form method=\"post\"><textarea name=\"comment\" autofocus=\"true\" placeholder=\"your comment ...\" rows=\"5\" cols=\"80\"></textarea><br/><input type=\"submit\" value=\"send comment\"></form>") +} + +func getRoot(w http.ResponseWriter, r *http.Request) { + httpHits.With(prometheus.Labels{"route": "/", "method": r.Method}).Inc() + io.WriteString(w, "<!doctype html><html><head><title>CommentsInteractor</title></head><body>") + if r.Method == http.MethodPost { + sendComment(w, r) + } + showDashboard(w, r) + io.WriteString(w, "</body></html>") +} + +func sendError(w http.ResponseWriter, err error, additionalInfo string) { + 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 main() { + http.HandleFunc("/", getRoot) + prometheus.Unregister(prometheus.NewGoCollector()) + http.Handle("/metrics", promhttp.Handler()) + + listeningHostPort := getenv("HOST", "127.0.0.1") + ":" + getenv("PORT", "9000") + comments_engine_endpoint = getenv("COMMENTS_ENGINE_ENDPOINT", "127.0.0.1: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) + } +} |
