From 20b65fd88550376335f9f8172187a9fa7a847311 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Wed, 30 Apr 2025 22:30:01 +0800 Subject: [PATCH] support event stream --- cmd/webserver.go | 12 ++++++++ pkg/core/handler.go | 3 ++ pkg/utils/api.go | 70 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/cmd/webserver.go b/cmd/webserver.go index e622ceda..5d5143b7 100644 --- a/cmd/webserver.go +++ b/cmd/webserver.go @@ -420,6 +420,18 @@ func bindApiWithTokenUpdate(fn core.ApiHandlerFunc, config *settings.Config) gin } } +func bindEventStreamApi(fn core.EventStreamApiHandlerFunc) gin.HandlerFunc { + return func(ginCtx *gin.Context) { + c := core.WrapWebContext(ginCtx) + utils.SetEventStreamHeader(c) + err := fn(c) + + if err != nil { + utils.WriteEventStreamJsonErrorResult(c, err) + } + } +} + func bindCachedJs(fn core.DataHandlerFunc, store persistence.CacheStore) gin.HandlerFunc { return cache.CachePage(store, time.Minute, func(ginCtx *gin.Context) { c := core.WrapWebContext(ginCtx) diff --git a/pkg/core/handler.go b/pkg/core/handler.go index d9d7f150..2f6a1358 100644 --- a/pkg/core/handler.go +++ b/pkg/core/handler.go @@ -15,6 +15,9 @@ type MiddlewareHandlerFunc func(*WebContext) // ApiHandlerFunc represents the api handler function type ApiHandlerFunc func(*WebContext) (any, *errs.Error) +// EventStreamApiHandlerFunc represents the event stream api handler function +type EventStreamApiHandlerFunc func(*WebContext) *errs.Error + // DataHandlerFunc represents the handler function that returns file data byte array and file name type DataHandlerFunc func(*WebContext) ([]byte, string, *errs.Error) diff --git a/pkg/utils/api.go b/pkg/utils/api.go index 0d12b177..be82a23b 100644 --- a/pkg/utils/api.go +++ b/pkg/utils/api.go @@ -1,6 +1,7 @@ package utils import ( + "encoding/json" "net/http" "reflect" @@ -80,6 +81,75 @@ func PrintDataErrorResult(c *core.WebContext, contentType string, err *errs.Erro c.Abort() } +// SetEventStreamHeader sets the headers for event stream response +func SetEventStreamHeader(c *core.WebContext) { + c.Writer.Header().Set("Content-Type", "text/event-stream") + c.Writer.Header().Set("Cache-Control", "no-cache") + c.Writer.Header().Set("Connection", "keep-alive") +} + +func WriteEventStreamJsonSuccessResult(c *core.WebContext, result any) { + data, err := json.Marshal(result) + + if err != nil { + c.Abort() + return + } + + _, err = c.Writer.WriteString("data: " + string(data) + "\n\n") + + if err != nil { + c.Abort() + return + } + + c.Writer.Flush() +} + +func WriteEventStreamJsonErrorResult(c *core.WebContext, originalErr *errs.Error) { + c.SetResponseError(originalErr) + + errorMessage := originalErr.Error() + + if originalErr.Code() == errs.ErrIncompleteOrIncorrectSubmission.Code() && len(originalErr.BaseError) > 0 { + validationErrors, ok := originalErr.BaseError[0].(validator.ValidationErrors) + + if ok { + for _, err := range validationErrors { + errorMessage = getValidationErrorText(err) + break + } + } + } + + result := gin.H{ + "success": false, + "errorCode": originalErr.Code(), + "errorMessage": errorMessage, + "path": c.Request.URL.Path, + } + + if originalErr.Context != nil { + result["context"] = originalErr.Context + } + + data, err := json.Marshal(result) + + if err != nil { + c.Abort() + return + } + + _, err = c.Writer.WriteString("data: " + string(data) + "\n\n") + + if err != nil { + c.Abort() + return + } + + c.Writer.Flush() +} + func getValidationErrorText(err validator.FieldError) string { fieldName := GetFirstLowerCharString(err.Field())