From b6e3be945680d017874967ae72ef86ee4235dcc2 Mon Sep 17 00:00:00 2001 From: Jeff Halter <868228+jhalter@users.noreply.github.com> Date: Thu, 18 Jul 2024 16:15:08 -0700 Subject: Add initial HTTP API endpoints --- internal/mobius/api.go | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 internal/mobius/api.go (limited to 'internal') diff --git a/internal/mobius/api.go b/internal/mobius/api.go new file mode 100644 index 0000000..31755b8 --- /dev/null +++ b/internal/mobius/api.go @@ -0,0 +1,96 @@ +package mobius + +import ( + "bytes" + "encoding/json" + "github.com/jhalter/mobius/hotline" + "io" + "log" + "log/slog" + "net/http" +) + +type logResponseWriter struct { + http.ResponseWriter + statusCode int + buf bytes.Buffer +} + +func NewLogResponseWriter(w http.ResponseWriter) *logResponseWriter { + return &logResponseWriter{w, http.StatusOK, bytes.Buffer{}} +} + +func (lrw *logResponseWriter) WriteHeader(code int) { + lrw.statusCode = code + lrw.ResponseWriter.WriteHeader(code) +} + +func (lrw *logResponseWriter) Write(b []byte) (int, error) { + lrw.buf.Write(b) + return lrw.ResponseWriter.Write(b) +} + +type APIServer struct { + hlServer *hotline.Server + logger *slog.Logger + mux *http.ServeMux +} + +func (srv *APIServer) logMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + lrw := NewLogResponseWriter(w) + next.ServeHTTP(lrw, r) + + srv.logger.Info("req", "method", r.Method, "url", r.URL.Path, "remoteAddr", r.RemoteAddr, "response_code", lrw.statusCode) + }) +} + +func NewAPIServer(hlServer *hotline.Server, reloadFunc func(), logger *slog.Logger) *APIServer { + srv := APIServer{ + hlServer: hlServer, + logger: logger, + mux: http.NewServeMux(), + } + + srv.mux.Handle("/api/v1/reload", srv.logMiddleware(http.HandlerFunc(srv.ReloadHandler(reloadFunc)))) + srv.mux.Handle("/api/v1/shutdown", srv.logMiddleware(http.HandlerFunc(srv.ShutdownHandler))) + srv.mux.Handle("/api/v1/stats", srv.logMiddleware(http.HandlerFunc(srv.RenderStats))) + + return &srv +} + +func (srv *APIServer) ShutdownHandler(w http.ResponseWriter, r *http.Request) { + msg, err := io.ReadAll(r.Body) + if err != nil || len(msg) == 0 { + w.WriteHeader(http.StatusBadRequest) + return + } + + go srv.hlServer.Shutdown(msg) + + _, _ = io.WriteString(w, `{ "msg": "server shutting down" }`) +} + +func (srv *APIServer) ReloadHandler(reloadFunc func()) func(w http.ResponseWriter, _ *http.Request) { + return func(w http.ResponseWriter, _ *http.Request) { + reloadFunc() + + _, _ = io.WriteString(w, `{ "msg": "config reloaded" }`) + } +} + +func (srv *APIServer) RenderStats(w http.ResponseWriter, _ *http.Request) { + u, err := json.Marshal(srv.hlServer.CurrentStats()) + if err != nil { + panic(err) + } + + _, _ = io.WriteString(w, string(u)) +} + +func (srv *APIServer) Serve(port string) { + err := http.ListenAndServe(port, srv.mux) + if err != nil { + log.Fatal(err) + } +} -- cgit