/
base.go
137 lines (112 loc) · 2.81 KB
/
base.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package actions
import (
"net/http"
gctx "github.com/goji/context"
"github.com/stellar/go/services/horizon/internal/render"
"github.com/stellar/go/services/horizon/internal/render/problem"
"github.com/stellar/go/services/horizon/internal/render/sse"
"github.com/zenazn/goji/web"
"golang.org/x/net/context"
)
// Base is a helper struct you can use as part of a custom action via
// composition.
//
// TODO: example usage
type Base struct {
Ctx context.Context
GojiCtx web.C
W http.ResponseWriter
R *http.Request
Err error
isSetup bool
}
// Prepare established the common attributes that get used in nearly every
// action. "Child" actions may override this method to extend action, but it
// is advised you also call this implementation to maintain behavior.
func (base *Base) Prepare(c web.C, w http.ResponseWriter, r *http.Request) {
base.Ctx = gctx.FromC(c)
base.GojiCtx = c
base.W = w
base.R = r
}
// Execute trigger content negottion and the actual execution of one of the
// action's handlers.
func (base *Base) Execute(action interface{}) {
contentType := render.Negotiate(base.Ctx, base.R)
switch contentType {
case render.MimeHal, render.MimeJSON:
action, ok := action.(JSON)
if !ok {
goto NotAcceptable
}
action.JSON()
if base.Err != nil {
problem.Render(base.Ctx, base.W, base.Err)
return
}
case render.MimeEventStream:
action, ok := action.(SSE)
if !ok {
goto NotAcceptable
}
stream := sse.NewStream(base.Ctx, base.W, base.R)
for {
action.SSE(stream)
if base.Err != nil {
// in the case that we haven't yet sent an event, is also means we
// havent sent the preamble, meaning we should simply return the normal
// error.
if stream.SentCount() == 0 {
problem.Render(base.Ctx, base.W, base.Err)
return
}
stream.Err(base.Err)
}
if stream.IsDone() {
return
}
select {
case <-base.Ctx.Done():
return
case <-sse.Pumped():
//no-op, continue onto the next iteration
}
}
case render.MimeRaw:
action, ok := action.(Raw)
if !ok {
goto NotAcceptable
}
action.Raw()
if base.Err != nil {
problem.Render(base.Ctx, base.W, base.Err)
return
}
default:
goto NotAcceptable
}
return
NotAcceptable:
problem.Render(base.Ctx, base.W, problem.NotAcceptable)
return
}
// Do executes the provided func iff there is no current error for the action.
// Provides a nicer way to invoke a set of steps that each may set `action.Err`
// during execution
func (base *Base) Do(fns ...func()) {
for _, fn := range fns {
if base.Err != nil {
return
}
fn()
}
}
// Setup runs the provided funcs if and only if no call to Setup() has been
// made previously on this action.
func (base *Base) Setup(fns ...func()) {
if base.isSetup {
return
}
base.Do(fns...)
base.isSetup = true
}