Skip to content

Commit 00cc153

Browse files
authored
fix: add http error responses (#494)
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.)
1 parent 2515771 commit 00cc153

31 files changed

+933
-655
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ sablier.yaml
33
node_modules
44
.DS_Store
55
*.wasm
6-
kubeconfig.yaml
6+
kubeconfig.yaml
7+
.idea

Makefile

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ GO_LDFLAGS := -s -w -X $(VPREFIX).Branch=$(GIT_BRANCH) -X $(VPREFIX).Version=$(V
1717
$(PLATFORMS):
1818
CGO_ENABLED=0 GOOS=$(os) GOARCH=$(arch) go build -trimpath -tags=nomsgpack -v -ldflags="${GO_LDFLAGS}" -o 'sablier_$(VERSION)_$(os)-$(arch)' .
1919

20+
run:
21+
go run main.go start
22+
2023
build:
2124
go build -v .
2225

app/http/middleware/logging.go

-78
This file was deleted.

app/http/routes/strategies.go

-165
Original file line numberDiff line numberDiff line change
@@ -1,180 +1,15 @@
11
package routes
22

33
import (
4-
"bufio"
5-
"bytes"
6-
"fmt"
7-
"net/http"
8-
"os"
9-
"sort"
10-
"strconv"
11-
"strings"
12-
13-
log "github.com/sirupsen/logrus"
14-
15-
"github.com/gin-gonic/gin"
16-
"github.com/sablierapp/sablier/app/http/routes/models"
17-
"github.com/sablierapp/sablier/app/instance"
184
"github.com/sablierapp/sablier/app/sessions"
195
"github.com/sablierapp/sablier/app/theme"
206
"github.com/sablierapp/sablier/config"
217
)
228

23-
var osDirFS = os.DirFS
24-
259
type ServeStrategy struct {
2610
Theme *theme.Themes
2711

2812
SessionsManager sessions.Manager
2913
StrategyConfig config.Strategy
3014
SessionsConfig config.Sessions
3115
}
32-
33-
func NewServeStrategy(sessionsManager sessions.Manager, strategyConf config.Strategy, sessionsConf config.Sessions, themes *theme.Themes) *ServeStrategy {
34-
35-
serveStrategy := &ServeStrategy{
36-
Theme: themes,
37-
SessionsManager: sessionsManager,
38-
StrategyConfig: strategyConf,
39-
SessionsConfig: sessionsConf,
40-
}
41-
42-
return serveStrategy
43-
}
44-
45-
func (s *ServeStrategy) ServeDynamic(c *gin.Context) {
46-
request := models.DynamicRequest{
47-
Theme: s.StrategyConfig.Dynamic.DefaultTheme,
48-
ShowDetails: s.StrategyConfig.Dynamic.ShowDetailsByDefault,
49-
RefreshFrequency: s.StrategyConfig.Dynamic.DefaultRefreshFrequency,
50-
SessionDuration: s.SessionsConfig.DefaultDuration,
51-
}
52-
53-
if err := c.ShouldBind(&request); err != nil {
54-
c.AbortWithError(http.StatusBadRequest, err)
55-
return
56-
}
57-
58-
var sessionState *sessions.SessionState
59-
if len(request.Names) > 0 {
60-
sessionState = s.SessionsManager.RequestSession(request.Names, request.SessionDuration)
61-
} else {
62-
sessionState = s.SessionsManager.RequestSessionGroup(request.Group, request.SessionDuration)
63-
}
64-
65-
if sessionState == nil {
66-
c.AbortWithStatus(http.StatusNotFound)
67-
return
68-
}
69-
70-
if sessionState.IsReady() {
71-
c.Header("X-Sablier-Session-Status", "ready")
72-
} else {
73-
c.Header("X-Sablier-Session-Status", "not-ready")
74-
}
75-
76-
renderOptions := theme.Options{
77-
DisplayName: request.DisplayName,
78-
ShowDetails: request.ShowDetails,
79-
SessionDuration: request.SessionDuration,
80-
RefreshFrequency: request.RefreshFrequency,
81-
InstanceStates: sessionStateToRenderOptionsInstanceState(sessionState),
82-
}
83-
84-
buf := new(bytes.Buffer)
85-
writer := bufio.NewWriter(buf)
86-
if err := s.Theme.Render(request.Theme, renderOptions, writer); err != nil {
87-
log.Error(err)
88-
c.AbortWithError(http.StatusInternalServerError, err)
89-
return
90-
}
91-
writer.Flush()
92-
93-
c.Header("Cache-Control", "no-cache")
94-
c.Header("Content-Type", "text/html")
95-
c.Header("Content-Length", strconv.Itoa(buf.Len()))
96-
c.Writer.Write(buf.Bytes())
97-
}
98-
99-
func (s *ServeStrategy) ServeBlocking(c *gin.Context) {
100-
request := models.BlockingRequest{
101-
Timeout: s.StrategyConfig.Blocking.DefaultTimeout,
102-
}
103-
104-
if err := c.ShouldBind(&request); err != nil {
105-
c.AbortWithError(http.StatusBadRequest, err)
106-
return
107-
}
108-
109-
var sessionState *sessions.SessionState
110-
var err error
111-
if len(request.Names) > 0 {
112-
sessionState, err = s.SessionsManager.RequestReadySession(c.Request.Context(), request.Names, request.SessionDuration, request.Timeout)
113-
} else {
114-
sessionState, err = s.SessionsManager.RequestReadySessionGroup(c.Request.Context(), request.Group, request.SessionDuration, request.Timeout)
115-
}
116-
117-
if err != nil {
118-
c.AbortWithError(http.StatusInternalServerError, err)
119-
return
120-
}
121-
122-
if sessionState == nil {
123-
c.AbortWithStatus(http.StatusNotFound)
124-
return
125-
}
126-
127-
if err != nil {
128-
c.Header("X-Sablier-Session-Status", "not-ready")
129-
c.JSON(http.StatusGatewayTimeout, map[string]interface{}{"error": err.Error()})
130-
return
131-
}
132-
133-
if sessionState.IsReady() {
134-
c.Header("X-Sablier-Session-Status", "ready")
135-
} else {
136-
c.Header("X-Sablier-Session-Status", "not-ready")
137-
}
138-
139-
c.JSON(http.StatusOK, map[string]interface{}{"session": sessionState})
140-
}
141-
142-
func sessionStateToRenderOptionsInstanceState(sessionState *sessions.SessionState) (instances []theme.Instance) {
143-
if sessionState == nil {
144-
log.Warnf("sessionStateToRenderOptionsInstanceState: sessionState is nil")
145-
return
146-
}
147-
sessionState.Instances.Range(func(key, value any) bool {
148-
if value != nil {
149-
instances = append(instances, instanceStateToRenderOptionsRequestState(value.(sessions.InstanceState).Instance))
150-
} else {
151-
log.Warnf("sessionStateToRenderOptionsInstanceState: sessionState instance is nil, key: %v", key)
152-
}
153-
154-
return true
155-
})
156-
157-
sort.SliceStable(instances, func(i, j int) bool {
158-
return strings.Compare(instances[i].Name, instances[j].Name) == -1
159-
})
160-
161-
return
162-
}
163-
164-
func instanceStateToRenderOptionsRequestState(instanceState *instance.State) theme.Instance {
165-
166-
var err error
167-
if instanceState.Message == "" {
168-
err = nil
169-
} else {
170-
err = fmt.Errorf(instanceState.Message)
171-
}
172-
173-
return theme.Instance{
174-
Name: instanceState.Name,
175-
Status: instanceState.Status,
176-
CurrentReplicas: instanceState.CurrentReplicas,
177-
DesiredReplicas: instanceState.DesiredReplicas,
178-
Error: err,
179-
}
180-
}

0 commit comments

Comments
 (0)