Skip to content

Commit

Permalink
fix: add http error responses (#494)
Browse files Browse the repository at this point in the history
This features adds rfc7807 Problem detail responses when an error happens processing a request.

This will greatly improve the common issues  with "blank pages" and "404 pages" issues which should now properly tell the user what input was wrong (group that does not exist, container name that does not exist, etc.)
  • Loading branch information
acouvreur authored Feb 2, 2025
1 parent 2515771 commit 00cc153
Show file tree
Hide file tree
Showing 31 changed files with 933 additions and 655 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ sablier.yaml
node_modules
.DS_Store
*.wasm
kubeconfig.yaml
kubeconfig.yaml
.idea
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ GO_LDFLAGS := -s -w -X $(VPREFIX).Branch=$(GIT_BRANCH) -X $(VPREFIX).Version=$(V
$(PLATFORMS):
CGO_ENABLED=0 GOOS=$(os) GOARCH=$(arch) go build -trimpath -tags=nomsgpack -v -ldflags="${GO_LDFLAGS}" -o 'sablier_$(VERSION)_$(os)-$(arch)' .

run:
go run main.go start

build:
go build -v .

Expand Down
78 changes: 0 additions & 78 deletions app/http/middleware/logging.go

This file was deleted.

165 changes: 0 additions & 165 deletions app/http/routes/strategies.go
Original file line number Diff line number Diff line change
@@ -1,180 +1,15 @@
package routes

import (
"bufio"
"bytes"
"fmt"
"net/http"
"os"
"sort"
"strconv"
"strings"

log "github.com/sirupsen/logrus"

"github.com/gin-gonic/gin"
"github.com/sablierapp/sablier/app/http/routes/models"
"github.com/sablierapp/sablier/app/instance"
"github.com/sablierapp/sablier/app/sessions"
"github.com/sablierapp/sablier/app/theme"
"github.com/sablierapp/sablier/config"
)

var osDirFS = os.DirFS

type ServeStrategy struct {
Theme *theme.Themes

SessionsManager sessions.Manager
StrategyConfig config.Strategy
SessionsConfig config.Sessions
}

func NewServeStrategy(sessionsManager sessions.Manager, strategyConf config.Strategy, sessionsConf config.Sessions, themes *theme.Themes) *ServeStrategy {

serveStrategy := &ServeStrategy{
Theme: themes,
SessionsManager: sessionsManager,
StrategyConfig: strategyConf,
SessionsConfig: sessionsConf,
}

return serveStrategy
}

func (s *ServeStrategy) ServeDynamic(c *gin.Context) {
request := models.DynamicRequest{
Theme: s.StrategyConfig.Dynamic.DefaultTheme,
ShowDetails: s.StrategyConfig.Dynamic.ShowDetailsByDefault,
RefreshFrequency: s.StrategyConfig.Dynamic.DefaultRefreshFrequency,
SessionDuration: s.SessionsConfig.DefaultDuration,
}

if err := c.ShouldBind(&request); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}

var sessionState *sessions.SessionState
if len(request.Names) > 0 {
sessionState = s.SessionsManager.RequestSession(request.Names, request.SessionDuration)
} else {
sessionState = s.SessionsManager.RequestSessionGroup(request.Group, request.SessionDuration)
}

if sessionState == nil {
c.AbortWithStatus(http.StatusNotFound)
return
}

if sessionState.IsReady() {
c.Header("X-Sablier-Session-Status", "ready")
} else {
c.Header("X-Sablier-Session-Status", "not-ready")
}

renderOptions := theme.Options{
DisplayName: request.DisplayName,
ShowDetails: request.ShowDetails,
SessionDuration: request.SessionDuration,
RefreshFrequency: request.RefreshFrequency,
InstanceStates: sessionStateToRenderOptionsInstanceState(sessionState),
}

buf := new(bytes.Buffer)
writer := bufio.NewWriter(buf)
if err := s.Theme.Render(request.Theme, renderOptions, writer); err != nil {
log.Error(err)
c.AbortWithError(http.StatusInternalServerError, err)
return
}
writer.Flush()

c.Header("Cache-Control", "no-cache")
c.Header("Content-Type", "text/html")
c.Header("Content-Length", strconv.Itoa(buf.Len()))
c.Writer.Write(buf.Bytes())
}

func (s *ServeStrategy) ServeBlocking(c *gin.Context) {
request := models.BlockingRequest{
Timeout: s.StrategyConfig.Blocking.DefaultTimeout,
}

if err := c.ShouldBind(&request); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}

var sessionState *sessions.SessionState
var err error
if len(request.Names) > 0 {
sessionState, err = s.SessionsManager.RequestReadySession(c.Request.Context(), request.Names, request.SessionDuration, request.Timeout)
} else {
sessionState, err = s.SessionsManager.RequestReadySessionGroup(c.Request.Context(), request.Group, request.SessionDuration, request.Timeout)
}

if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}

if sessionState == nil {
c.AbortWithStatus(http.StatusNotFound)
return
}

if err != nil {
c.Header("X-Sablier-Session-Status", "not-ready")
c.JSON(http.StatusGatewayTimeout, map[string]interface{}{"error": err.Error()})
return
}

if sessionState.IsReady() {
c.Header("X-Sablier-Session-Status", "ready")
} else {
c.Header("X-Sablier-Session-Status", "not-ready")
}

c.JSON(http.StatusOK, map[string]interface{}{"session": sessionState})
}

func sessionStateToRenderOptionsInstanceState(sessionState *sessions.SessionState) (instances []theme.Instance) {
if sessionState == nil {
log.Warnf("sessionStateToRenderOptionsInstanceState: sessionState is nil")
return
}
sessionState.Instances.Range(func(key, value any) bool {
if value != nil {
instances = append(instances, instanceStateToRenderOptionsRequestState(value.(sessions.InstanceState).Instance))
} else {
log.Warnf("sessionStateToRenderOptionsInstanceState: sessionState instance is nil, key: %v", key)
}

return true
})

sort.SliceStable(instances, func(i, j int) bool {
return strings.Compare(instances[i].Name, instances[j].Name) == -1
})

return
}

func instanceStateToRenderOptionsRequestState(instanceState *instance.State) theme.Instance {

var err error
if instanceState.Message == "" {
err = nil
} else {
err = fmt.Errorf(instanceState.Message)
}

return theme.Instance{
Name: instanceState.Name,
Status: instanceState.Status,
CurrentReplicas: instanceState.CurrentReplicas,
DesiredReplicas: instanceState.DesiredReplicas,
Error: err,
}
}
Loading

0 comments on commit 00cc153

Please sign in to comment.