From 4fa11e6a2aa5c54d172f7007cb228173335df5da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Thu, 16 Sep 2021 20:16:51 +0200 Subject: [PATCH] refactor HTTP error. --- internal/api/members/bluk.go | 11 ++- internal/api/members/controler.go | 16 ++-- internal/api/members/handler.go | 4 +- internal/api/room/broadcast.go | 8 +- internal/api/room/clipboard.go | 18 ++--- internal/api/room/control.go | 8 +- internal/api/room/handler.go | 2 +- internal/api/room/keyboard.go | 4 +- internal/api/room/screen.go | 10 +-- internal/api/room/upload.go | 38 ++++----- internal/api/router.go | 4 +- internal/api/session.go | 4 +- internal/http/auth/auth.go | 10 +-- internal/http/manager.go | 5 +- internal/utils/http.go | 126 ++++++++++++++++++++++-------- 15 files changed, 166 insertions(+), 102 deletions(-) diff --git a/internal/api/members/bluk.go b/internal/api/members/bluk.go index cb1695c4..d317b1a0 100644 --- a/internal/api/members/bluk.go +++ b/internal/api/members/bluk.go @@ -2,7 +2,6 @@ package members import ( "encoding/json" - "fmt" "io" "net/http" @@ -18,13 +17,13 @@ type MemberBulkUpdatePayload struct { func (h *MembersHandler) membersBulkUpdate(w http.ResponseWriter, r *http.Request) { bytes, err := io.ReadAll(r.Body) if err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpBadRequest(w).WithInternalErr(err).Msg("unable to read post body") return } header := &MemberBulkUpdatePayload{} if err := json.Unmarshal(bytes, &header); err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpBadRequest(w).WithInternalErr(err).Msg("unable to unmarshal payload") return } @@ -32,7 +31,7 @@ func (h *MembersHandler) membersBulkUpdate(w http.ResponseWriter, r *http.Reques // TODO: Bulk select? profile, err := h.members.Select(memberId) if err != nil { - utils.HttpInternalServerError(w, fmt.Sprintf("member %s: %v", memberId, err)) + utils.HttpInternalServerError(w, err).WithInternalMsg("unable to select member profile").Msgf("failed to update member %s", memberId) return } @@ -41,12 +40,12 @@ func (h *MembersHandler) membersBulkUpdate(w http.ResponseWriter, r *http.Reques } if err := json.Unmarshal(bytes, &body); err != nil { - utils.HttpInternalServerError(w, fmt.Sprintf("member %s: %v", memberId, err)) + utils.HttpBadRequest(w).WithInternalErr(err).Msgf("unable to unmarshal payload for member %s", memberId) return } if err := h.members.UpdateProfile(memberId, body.Profile); err != nil { - utils.HttpInternalServerError(w, fmt.Sprintf("member %s: %v", memberId, err)) + utils.HttpInternalServerError(w, err).WithInternalMsg("unable to update member profile").Msgf("failed to update member %s", memberId) return } } diff --git a/internal/api/members/controler.go b/internal/api/members/controler.go index 09f626d2..6e5990a4 100644 --- a/internal/api/members/controler.go +++ b/internal/api/members/controler.go @@ -39,7 +39,7 @@ func (h *MembersHandler) membersList(w http.ResponseWriter, r *http.Request) { entries, err := h.members.SelectAll(limit, offset) if err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).Send() return } @@ -72,21 +72,21 @@ func (h *MembersHandler) membersCreate(w http.ResponseWriter, r *http.Request) { } if data.Username == "" { - utils.HttpBadRequest(w, "username cannot be empty") + utils.HttpBadRequest(w).Msg("username cannot be empty") return } if data.Password == "" { - utils.HttpBadRequest(w, "password cannot be empty") + utils.HttpBadRequest(w).Msg("password cannot be empty") return } id, err := h.members.Insert(data.Username, data.Password, data.Profile) if err != nil { if errors.Is(err, types.ErrMemberAlreadyExists) { - utils.HttpUnprocessableEntity(w, err) + utils.HttpUnprocessableEntity(w).Msg("member already exists") } else { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).Send() } return } @@ -113,7 +113,7 @@ func (h *MembersHandler) membersUpdateProfile(w http.ResponseWriter, r *http.Req } if err := h.members.UpdateProfile(member.ID, profile); err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).Send() return } @@ -129,7 +129,7 @@ func (h *MembersHandler) membersUpdatePassword(w http.ResponseWriter, r *http.Re } if err := h.members.UpdatePassword(member.ID, data.Password); err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).Send() return } @@ -140,7 +140,7 @@ func (h *MembersHandler) membersDelete(w http.ResponseWriter, r *http.Request) { member := GetMember(r) if err := h.members.Delete(member.ID); err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).Send() return } diff --git a/internal/api/members/handler.go b/internal/api/members/handler.go index b5d52718..4c25f94e 100644 --- a/internal/api/members/handler.go +++ b/internal/api/members/handler.go @@ -71,9 +71,9 @@ func (h *MembersHandler) ExtractMember(next http.Handler) http.Handler { profile, err := h.members.Select(memberId) if err != nil { if errors.Is(err, types.ErrMemberDoesNotExist) { - utils.HttpNotFound(w, err) + utils.HttpNotFound(w).Msg("member not found") } else { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).Send() } return diff --git a/internal/api/room/broadcast.go b/internal/api/room/broadcast.go index d0639770..24c91a92 100644 --- a/internal/api/room/broadcast.go +++ b/internal/api/room/broadcast.go @@ -28,18 +28,18 @@ func (h *RoomHandler) boradcastStart(w http.ResponseWriter, r *http.Request) { } if data.URL == "" { - utils.HttpBadRequest(w, "missing broadcast URL") + utils.HttpBadRequest(w).Msg("missing broadcast URL") return } broadcast := h.capture.Broadcast() if broadcast.Started() { - utils.HttpUnprocessableEntity(w, "server is already broadcasting") + utils.HttpUnprocessableEntity(w).Msg("server is already broadcasting") return } if err := broadcast.Start(data.URL); err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).Send() return } @@ -56,7 +56,7 @@ func (h *RoomHandler) boradcastStart(w http.ResponseWriter, r *http.Request) { func (h *RoomHandler) boradcastStop(w http.ResponseWriter, r *http.Request) { broadcast := h.capture.Broadcast() if !broadcast.Started() { - utils.HttpUnprocessableEntity(w, "server is not broadcasting") + utils.HttpUnprocessableEntity(w).Msg("server is not broadcasting") return } diff --git a/internal/api/room/clipboard.go b/internal/api/room/clipboard.go index 4a70da3d..6c6dfcaf 100644 --- a/internal/api/room/clipboard.go +++ b/internal/api/room/clipboard.go @@ -18,7 +18,7 @@ type ClipboardPayload struct { func (h *RoomHandler) clipboardGetText(w http.ResponseWriter, r *http.Request) { data, err := h.desktop.ClipboardGetText() if err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).Send() return } @@ -40,7 +40,7 @@ func (h *RoomHandler) clipboardSetText(w http.ResponseWriter, r *http.Request) { }) if err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).Send() return } @@ -50,7 +50,7 @@ func (h *RoomHandler) clipboardSetText(w http.ResponseWriter, r *http.Request) { func (h *RoomHandler) clipboardGetImage(w http.ResponseWriter, r *http.Request) { bytes, err := h.desktop.ClipboardGetBinary("image/png") if err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).Send() return } @@ -64,7 +64,7 @@ func (h *RoomHandler) clipboardGetImage(w http.ResponseWriter, r *http.Request) func (h *RoomHandler) clipboardSetImage(w http.ResponseWriter, r *http.Request) { err := r.ParseMultipartForm(MAX_UPLOAD_SIZE) if err != nil { - utils.HttpBadRequest(w, "failed to parse multipart form") + utils.HttpBadRequest(w).WithInternalErr(err).Msg("failed to parse multipart form") return } @@ -73,7 +73,7 @@ func (h *RoomHandler) clipboardSetImage(w http.ResponseWriter, r *http.Request) file, header, err := r.FormFile("file") if err != nil { - utils.HttpBadRequest(w, "no file received") + utils.HttpBadRequest(w).WithInternalErr(err).Msg("no file received") return } @@ -81,20 +81,20 @@ func (h *RoomHandler) clipboardSetImage(w http.ResponseWriter, r *http.Request) mime := header.Header.Get("Content-Type") if !strings.HasPrefix(mime, "image/") { - utils.HttpBadRequest(w, "file must be image") + utils.HttpBadRequest(w).Msg("file must be image") return } buffer := new(bytes.Buffer) _, err = buffer.ReadFrom(file) if err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).WithInternalMsg("unable to read from uploaded file").Send() return } err = h.desktop.ClipboardSetBinary("image/png", buffer.Bytes()) if err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).WithInternalMsg("unable set image to clipboard").Send() return } @@ -104,7 +104,7 @@ func (h *RoomHandler) clipboardSetImage(w http.ResponseWriter, r *http.Request) func (h *RoomHandler) clipboardGetTargets(w http.ResponseWriter, r *http.Request) { targets, err := h.desktop.ClipboardGetTargets() if err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).Send() return } diff --git a/internal/api/room/control.go b/internal/api/room/control.go index 0bd5518a..b4aeb5e1 100644 --- a/internal/api/room/control.go +++ b/internal/api/room/control.go @@ -36,7 +36,7 @@ func (h *RoomHandler) controlStatus(w http.ResponseWriter, r *http.Request) { func (h *RoomHandler) controlRequest(w http.ResponseWriter, r *http.Request) { host := h.sessions.GetHost() if host != nil { - utils.HttpUnprocessableEntity(w, "there is already a host") + utils.HttpUnprocessableEntity(w).Msg("there is already a host") return } @@ -49,7 +49,7 @@ func (h *RoomHandler) controlRequest(w http.ResponseWriter, r *http.Request) { func (h *RoomHandler) controlRelease(w http.ResponseWriter, r *http.Request) { session := auth.GetSession(r) if !session.IsHost() { - utils.HttpUnprocessableEntity(w, "session is not the host") + utils.HttpUnprocessableEntity(w).Msg("session is not the host") return } @@ -71,12 +71,12 @@ func (h *RoomHandler) controlGive(w http.ResponseWriter, r *http.Request) { target, ok := h.sessions.Get(sessionId) if !ok { - utils.HttpNotFound(w, "target session was not found") + utils.HttpNotFound(w).Msg("target session was not found") return } if !target.Profile().CanHost { - utils.HttpBadRequest(w, "target session is not allowed to host") + utils.HttpBadRequest(w).Msg("target session is not allowed to host") return } diff --git a/internal/api/room/handler.go b/internal/api/room/handler.go index dc1e88d9..91f0c52a 100644 --- a/internal/api/room/handler.go +++ b/internal/api/room/handler.go @@ -90,7 +90,7 @@ func (h *RoomHandler) uploadMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { session := auth.GetSession(r) if !session.IsHost() && (!session.Profile().CanHost || !h.sessions.ImplicitHosting()) { - utils.HttpForbidden(w, "without implicit hosting, only host can upload files") + utils.HttpForbidden(w).Msg("without implicit hosting, only host can upload files") } else { next.ServeHTTP(w, r) } diff --git a/internal/api/room/keyboard.go b/internal/api/room/keyboard.go index dd1b4c72..289faf97 100644 --- a/internal/api/room/keyboard.go +++ b/internal/api/room/keyboard.go @@ -29,7 +29,7 @@ func (h *RoomHandler) keyboardMapSet(w http.ResponseWriter, r *http.Request) { }) if err != nil { - utils.HttpInternalServerError(w, "unable to change keyboard map") + utils.HttpInternalServerError(w, err).Send() return } @@ -40,7 +40,7 @@ func (h *RoomHandler) keyboardMapGet(w http.ResponseWriter, r *http.Request) { data, err := h.desktop.GetKeyboardMap() if err != nil { - utils.HttpInternalServerError(w, "unable to get keyboard map") + utils.HttpInternalServerError(w, err).Send() return } diff --git a/internal/api/room/screen.go b/internal/api/room/screen.go index b58b8948..925702dd 100644 --- a/internal/api/room/screen.go +++ b/internal/api/room/screen.go @@ -20,7 +20,7 @@ func (h *RoomHandler) screenConfiguration(w http.ResponseWriter, r *http.Request size := h.desktop.GetScreenSize() if size == nil { - utils.HttpInternalServerError(w, "unable to get screen configuration") + utils.HttpInternalServerError(w, nil).WithInternalMsg("unable to get screen configuration").Send() return } @@ -42,7 +42,7 @@ func (h *RoomHandler) screenConfigurationChange(w http.ResponseWriter, r *http.R Height: data.Height, Rate: data.Rate, }); err != nil { - utils.HttpUnprocessableEntity(w, err) + utils.HttpUnprocessableEntity(w).WithInternalErr(err).Msg("cannot set screen size") return } @@ -83,7 +83,7 @@ func (h *RoomHandler) screenShotGet(w http.ResponseWriter, r *http.Request) { img := h.desktop.GetScreenshotImage() bytes, err := utils.CreateJPGImage(img, quality) if err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).Send() return } @@ -96,13 +96,13 @@ func (h *RoomHandler) screenShotGet(w http.ResponseWriter, r *http.Request) { func (h *RoomHandler) screenCastGet(w http.ResponseWriter, r *http.Request) { screencast := h.capture.Screencast() if !screencast.Enabled() { - utils.HttpBadRequest(w, "screencast pipeline is not enabled") + utils.HttpBadRequest(w).Msg("screencast pipeline is not enabled") return } bytes, err := screencast.Image() if err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).Send() return } diff --git a/internal/api/room/upload.go b/internal/api/room/upload.go index b4c51fb7..c4afa22d 100644 --- a/internal/api/room/upload.go +++ b/internal/api/room/upload.go @@ -10,13 +10,15 @@ import ( "demodesk/neko/internal/utils" ) +// TODO: Extract file uploading to custom utility. + // maximum upload size of 32 MB const maxUploadSize = 32 << 20 func (h *RoomHandler) uploadDrop(w http.ResponseWriter, r *http.Request) { err := r.ParseMultipartForm(maxUploadSize) if err != nil { - utils.HttpBadRequest(w, "failed to parse multipart form") + utils.HttpBadRequest(w).WithInternalErr(err).Msg("failed to parse multipart form") return } @@ -25,25 +27,25 @@ func (h *RoomHandler) uploadDrop(w http.ResponseWriter, r *http.Request) { X, err := strconv.Atoi(r.FormValue("x")) if err != nil { - utils.HttpBadRequest(w, "no X coordinate received") + utils.HttpBadRequest(w).WithInternalErr(err).Msg("no X coordinate received") return } Y, err := strconv.Atoi(r.FormValue("y")) if err != nil { - utils.HttpBadRequest(w, "no Y coordinate received") + utils.HttpBadRequest(w).WithInternalErr(err).Msg("no Y coordinate received") return } req_files := r.MultipartForm.File["files"] if len(req_files) == 0 { - utils.HttpBadRequest(w, "no files received") + utils.HttpBadRequest(w).Msg("no files received") return } dir, err := os.MkdirTemp("", "neko-drop-*") if err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).WithInternalMsg("unable to create temporary directory").Send() return } @@ -53,7 +55,7 @@ func (h *RoomHandler) uploadDrop(w http.ResponseWriter, r *http.Request) { srcFile, err := req_file.Open() if err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).WithInternalMsg("unable to open uploaded file").Send() return } @@ -61,7 +63,7 @@ func (h *RoomHandler) uploadDrop(w http.ResponseWriter, r *http.Request) { dstFile, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).WithInternalMsg("unable to open destination file").Send() return } @@ -69,7 +71,7 @@ func (h *RoomHandler) uploadDrop(w http.ResponseWriter, r *http.Request) { _, err = io.Copy(dstFile, srcFile) if err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).WithInternalMsg("unable to copy uploaded file to destination file").Send() return } @@ -77,7 +79,7 @@ func (h *RoomHandler) uploadDrop(w http.ResponseWriter, r *http.Request) { } if !h.desktop.DropFiles(X, Y, files) { - utils.HttpInternalServerError(w, "unable to drop files") + utils.HttpInternalServerError(w, nil).WithInternalMsg("unable to drop files").Send() return } @@ -87,7 +89,7 @@ func (h *RoomHandler) uploadDrop(w http.ResponseWriter, r *http.Request) { func (h *RoomHandler) uploadDialogPost(w http.ResponseWriter, r *http.Request) { err := r.ParseMultipartForm(maxUploadSize) if err != nil { - utils.HttpBadRequest(w, "failed to parse multipart form") + utils.HttpBadRequest(w).WithInternalErr(err).Msg("failed to parse multipart form") return } @@ -95,19 +97,19 @@ func (h *RoomHandler) uploadDialogPost(w http.ResponseWriter, r *http.Request) { defer r.MultipartForm.RemoveAll() if !h.desktop.IsFileChooserDialogOpened() { - utils.HttpBadRequest(w, "open file chooser dialog first") + utils.HttpUnprocessableEntity(w).Msg("file chooser dialog is not open") return } req_files := r.MultipartForm.File["files"] if len(req_files) == 0 { - utils.HttpBadRequest(w, "no files received") + utils.HttpInternalServerError(w, err).WithInternalMsg("unable to copy uploaded file to destination file").Send() return } dir, err := os.MkdirTemp("", "neko-dialog-*") if err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).WithInternalMsg("unable to create temporary directory").Send() return } @@ -116,7 +118,7 @@ func (h *RoomHandler) uploadDialogPost(w http.ResponseWriter, r *http.Request) { srcFile, err := req_file.Open() if err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).WithInternalMsg("unable to open uploaded file").Send() return } @@ -124,7 +126,7 @@ func (h *RoomHandler) uploadDialogPost(w http.ResponseWriter, r *http.Request) { dstFile, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).WithInternalMsg("unable to open destination file").Send() return } @@ -132,13 +134,13 @@ func (h *RoomHandler) uploadDialogPost(w http.ResponseWriter, r *http.Request) { _, err = io.Copy(dstFile, srcFile) if err != nil { - utils.HttpInternalServerError(w, err) + utils.HttpInternalServerError(w, err).WithInternalMsg("unable to copy uploaded file to destination file").Send() return } } if err := h.desktop.HandleFileChooserDialog(dir); err != nil { - utils.HttpInternalServerError(w, "unable to handle file chooser dialog") + utils.HttpInternalServerError(w, err).WithInternalMsg("unable to handle file chooser dialog").Send() return } @@ -147,7 +149,7 @@ func (h *RoomHandler) uploadDialogPost(w http.ResponseWriter, r *http.Request) { func (h *RoomHandler) uploadDialogClose(w http.ResponseWriter, r *http.Request) { if !h.desktop.IsFileChooserDialogOpened() { - utils.HttpBadRequest(w, "file chooser dialog is not open") + utils.HttpUnprocessableEntity(w).Msg("file chooser dialog is not open") return } diff --git a/internal/api/router.go b/internal/api/router.go index d37dd00b..b0e49619 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -76,9 +76,9 @@ func (api *ApiManagerCtx) Authenticate(next http.Handler) http.Handler { } if errors.Is(err, types.ErrSessionLoginDisabled) { - utils.HttpForbidden(w, err) + utils.HttpForbidden(w).Msg("login is disabled for this session") } else { - utils.HttpUnauthorized(w, err) + utils.HttpUnauthorized(w).WithInternalErr(err).Send() } return diff --git a/internal/api/session.go b/internal/api/session.go index aa1f0a31..79e8c294 100644 --- a/internal/api/session.go +++ b/internal/api/session.go @@ -28,7 +28,7 @@ func (api *ApiManagerCtx) Login(w http.ResponseWriter, r *http.Request) { session, token, err := api.members.Login(data.Username, data.Password) if err != nil { - utils.HttpUnauthorized(w, err) + utils.HttpUnauthorized(w).WithInternalErr(err).Send() return } @@ -52,7 +52,7 @@ func (api *ApiManagerCtx) Logout(w http.ResponseWriter, r *http.Request) { err := api.members.Logout(session.ID()) if err != nil { - utils.HttpUnauthorized(w, err) + utils.HttpUnauthorized(w).WithInternalErr(err).Send() return } diff --git a/internal/http/auth/auth.go b/internal/http/auth/auth.go index 856a6913..9d25ad90 100644 --- a/internal/http/auth/auth.go +++ b/internal/http/auth/auth.go @@ -25,7 +25,7 @@ func AdminsOnly(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { session := GetSession(r) if !session.Profile().IsAdmin { - utils.HttpForbidden(w) + utils.HttpForbidden(w).Msg("session is not admin") } else { next.ServeHTTP(w, r) } @@ -36,7 +36,7 @@ func HostsOnly(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { session := GetSession(r) if !session.IsHost() { - utils.HttpForbidden(w, "only host can do this") + utils.HttpForbidden(w).Msg("session is not host") } else { next.ServeHTTP(w, r) } @@ -47,7 +47,7 @@ func CanWatchOnly(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { session := GetSession(r) if !session.Profile().CanWatch { - utils.HttpForbidden(w) + utils.HttpForbidden(w).Msg("session cannot watch") } else { next.ServeHTTP(w, r) } @@ -58,7 +58,7 @@ func CanHostOnly(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { session := GetSession(r) if !session.Profile().CanHost { - utils.HttpForbidden(w) + utils.HttpForbidden(w).Msg("session cannot host") } else { next.ServeHTTP(w, r) } @@ -69,7 +69,7 @@ func CanAccessClipboardOnly(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { session := GetSession(r) if !session.Profile().CanAccessClipboard { - utils.HttpForbidden(w) + utils.HttpForbidden(w).Msg("session cannot access clipboard") } else { next.ServeHTTP(w, r) } diff --git a/internal/http/manager.go b/internal/http/manager.go index 51d3cd1d..47a461d8 100644 --- a/internal/http/manager.go +++ b/internal/http/manager.go @@ -13,7 +13,6 @@ import ( "demodesk/neko/internal/config" "demodesk/neko/internal/types" - "demodesk/neko/internal/utils" ) type HttpManagerCtx struct { @@ -55,13 +54,13 @@ func New(WebSocketManager types.WebSocketManager, ApiManager types.ApiManager, c if _, err := os.Stat(config.Static + r.URL.Path); !os.IsNotExist(err) { fs.ServeHTTP(w, r) } else { - utils.HttpNotFound(w) + http.NotFound(w, r) } }) } router.NotFound(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - utils.HttpNotFound(w) + http.NotFound(w, r) })) return &HttpManagerCtx{ diff --git a/internal/utils/http.go b/internal/utils/http.go index d2e6c382..0056940d 100644 --- a/internal/utils/http.go +++ b/internal/utils/http.go @@ -9,16 +9,12 @@ import ( "github.com/rs/zerolog/log" ) -type ErrResponse struct { - Message string `json:"message"` -} - func HttpJsonRequest(w http.ResponseWriter, r *http.Request, res interface{}) bool { if err := json.NewDecoder(r.Body).Decode(res); err != nil { if err == io.EOF { - HttpBadRequest(w, "no data provided") + HttpBadRequest(w).WithInternalErr(err).Msg("no data provided") } else { - HttpBadRequest(w, err) + HttpBadRequest(w).WithInternalErr(err).Msg("unable to parse provided data") } return false @@ -27,21 +23,15 @@ func HttpJsonRequest(w http.ResponseWriter, r *http.Request, res interface{}) bo return true } -func HttpJsonResponse(w http.ResponseWriter, status int, res interface{}) { +func HttpJsonResponse(w http.ResponseWriter, code int, res interface{}) { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) + w.WriteHeader(code) if err := json.NewEncoder(w).Encode(res); err != nil { log.Err(err).Str("module", "http").Msg("sending http json response failed") } } -func HttpError(w http.ResponseWriter, status int, res interface{}) { - HttpJsonResponse(w, status, &ErrResponse{ - Message: fmt.Sprint(res), - }) -} - func HttpSuccess(w http.ResponseWriter, res ...interface{}) { if len(res) == 0 { w.WriteHeader(http.StatusNoContent) @@ -50,34 +40,108 @@ func HttpSuccess(w http.ResponseWriter, res ...interface{}) { } } -func HttpBadRequest(w http.ResponseWriter, res ...interface{}) { - defHttpError(w, http.StatusBadRequest, "bad request", res...) +// HTTPError is an error with a message and an HTTP status code. +type HTTPError struct { + Code int `json:"code"` + Message string `json:"message"` + + InternalErr error `json:"-"` + InternalMsg string `json:"-"` + + w http.ResponseWriter `json:"-"` } -func HttpUnauthorized(w http.ResponseWriter, res ...interface{}) { - defHttpError(w, http.StatusUnauthorized, "invalid or missing access token", res...) +func (e *HTTPError) Error() string { + if e.InternalMsg != "" { + return e.InternalMsg + } + return fmt.Sprintf("%d: %s", e.Code, e.Message) } -func HttpForbidden(w http.ResponseWriter, res ...interface{}) { - defHttpError(w, http.StatusForbidden, "access token does not have the required scope", res...) +func (e *HTTPError) Cause() error { + if e.InternalErr != nil { + return e.InternalErr + } + return e } -func HttpNotFound(w http.ResponseWriter, res ...interface{}) { - defHttpError(w, http.StatusNotFound, "resource not found", res...) +// WithInternalErr adds internal error information to the error +func (e *HTTPError) WithInternalErr(err error) *HTTPError { + e.InternalErr = err + return e } -func HttpUnprocessableEntity(w http.ResponseWriter, res ...interface{}) { - defHttpError(w, http.StatusUnprocessableEntity, "unprocessable entity", res...) +// WithInternalMsg adds internal message information to the error +func (e *HTTPError) WithInternalMsg(msg string) *HTTPError { + e.InternalMsg = msg + return e } -func HttpInternalServerError(w http.ResponseWriter, res ...interface{}) { - defHttpError(w, http.StatusInternalServerError, "internal server error", res...) +// WithInternalMsg adds internal formated message information to the error +func (e *HTTPError) WithInternalMsgf(fmtStr string, args ...interface{}) *HTTPError { + e.InternalMsg = fmt.Sprintf(fmtStr, args...) + return e } -func defHttpError(w http.ResponseWriter, status int, text string, res ...interface{}) { - if len(res) == 0 { - HttpError(w, status, text) - } else { - HttpError(w, status, res[0]) +// Sends error with custom formated message +func (e *HTTPError) Msgf(fmtSt string, args ...interface{}) { + e.Message = fmt.Sprintf(fmtSt, args...) + e.Send() +} + +// Sends error with custom message +func (e *HTTPError) Msg(str string) { + e.Message = str + e.Send() +} + +// Sends error with default status text +func (e *HTTPError) Send() { + if e.Message == "" { + e.Message = http.StatusText(e.Code) + } + + logger := log.Error(). + Err(e.InternalErr). + Str("module", "http"). + Int("code", e.Code) + + message := e.Message + if e.InternalMsg != "" { + message = e.InternalMsg + } + + logger.Msg(message) + HttpJsonResponse(e.w, e.Code, e) +} + +func HttpError(w http.ResponseWriter, code int) *HTTPError { + return &HTTPError{ + Code: code, + w: w, } } + +func HttpBadRequest(w http.ResponseWriter) *HTTPError { + return HttpError(w, http.StatusBadRequest) +} + +func HttpUnauthorized(w http.ResponseWriter) *HTTPError { + return HttpError(w, http.StatusUnauthorized) +} + +func HttpForbidden(w http.ResponseWriter) *HTTPError { + return HttpError(w, http.StatusForbidden) +} + +func HttpNotFound(w http.ResponseWriter) *HTTPError { + return HttpError(w, http.StatusNotFound) +} + +func HttpUnprocessableEntity(w http.ResponseWriter) *HTTPError { + return HttpError(w, http.StatusUnprocessableEntity) +} + +func HttpInternalServerError(w http.ResponseWriter, err error) *HTTPError { + return HttpError(w, http.StatusInternalServerError).WithInternalErr(err) +}