This repository has been archived by the owner on Dec 20, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
handler.go
164 lines (144 loc) · 5.46 KB
/
handler.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package congo
import (
"html/template"
"io"
"net/http"
"sync"
)
// HandlerActions accept a Context interface and optionally return that
// same Context back or another implementation of Context paired with
// any of the responses available in responses.go as the second return value
// (optionally nil). HandlerActions that return a response will cause the
// chain of actions to halt for its particular Handler when invoked.
type HandlerAction (func(Context) (Context, interface{}))
type Handler struct {
templateStore *template.Template
actions []HandlerAction
actionMutex sync.RWMutex
}
// Creates a new handler and returns a pointer to it.
func NewHandler() *Handler {
return &Handler{
templateStore: defaultTemplateStore,
actions: make([]HandlerAction, 0),
}
}
// Returns the currently defined template store for this Handler.
func (h *Handler) TemplateStore() *template.Template {
return h.templateStore
}
// Sets the template store for the given handler. Note that when rendering
// templates within other templates you will be restricted to those registered
// with the store of top-level template. Therefore it is best to keep only
// one template store with all of your templates registered in it. There is a
// method SetDefaultTemplateStore which need only be set once to have the
// provided template store applied to all Handlers.
func (h *Handler) SetTemplateStore(store *template.Template) *Handler {
h.templateStore = store
return h
}
// Appends a HandlerActions to the action chain for this Handler. See the
// type description of HandlerAction for details.
func (h *Handler) Actions(a ...HandlerAction) *Handler {
h.actionMutex.Lock()
defer h.actionMutex.Unlock()
for _, action := range a {
h.actions = append(h.actions, action)
}
return h
}
// Copies the HandlerActions and template store for a Handler to a new
// instance and returns it.
func (h *Handler) Copy() *Handler {
copy := NewHandler()
copy.Actions(h.actions...)
copy.templateStore = h.templateStore
return copy
}
// Runs the chain of HandlerActions beginning with the given context. The
// return values from this represent the final context and response given
// by the last HandlerAction to return a response.
func (h *Handler) applyActions(context Context) (Context, interface{}) {
var response interface{} = nil
h.actionMutex.RLock()
defer h.actionMutex.RUnlock()
for _, action := range h.actions {
newContext, newResponse := action(context)
// If the context return value is non-nil, check that it still implements
// the Context interface. If not, the method will panic.
if newContext != nil {
if assertContext, ok := newContext.(Context); ok {
context = assertContext
} else {
panic("Resulting context does not implement congo.Context")
}
}
// If an HandlerAction has returned a response then halt the chain.
if newResponse != nil {
response = newResponse
break
}
}
return context, response
}
// Returns a function that is compatible with the standard http Handler.
func MuxHandler(h *Handler) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
context, response := h.applyActions(NewBaseContext(w, r))
// We must have a response in order to do anything.
if response == nil {
panic("No response given after handler action chain executed")
}
h.responseRenderStep(context, response)
h.responseFinalizeStep(context, response)
}
}
// Performs any rendering needed for a given response. This method is only
// concerned with any template rendering or related activity. Any
// response types that do not deal with these activities are ignored here.
func (h *Handler) responseRenderStep(context Context, response interface{}) {
switch response.(type) {
case *RenderResponse:
// In this step, the inner template is rendered and written to the context
// where it's output is available from context.Content()
r := response.(*RenderResponse)
h.execTemplate(r.Template, context, context)
case *NotFoundResponse:
r := response.(*NotFoundResponse)
h.execTemplate(r.Template, context, context)
}
}
// Performs the finalizing step(s) for a given response. This includes
// sending appropriate headers etc. as well as rendering the layouts with the
// contents of responses handled by responseRenderStep.
func (h *Handler) responseFinalizeStep(context Context, response interface{}) {
switch response.(type) {
default:
panic("Unknown response type")
case *NullResponse:
case *RenderResponse:
// In this step, the layout template is rendered which should make use of
// the content from the render step by using {{.Content}} in the template.
r := response.(*RenderResponse)
h.execTemplate(r.Layout, context.ResponseWriter(), context)
case *RedirectResponse:
r := response.(*RedirectResponse)
http.Redirect(context.ResponseWriter(), context.Request(),
r.Path, http.StatusFound)
case *NotFoundResponse:
r := response.(*NotFoundResponse)
context.ResponseWriter().WriteHeader(http.StatusNotFound)
h.execTemplate(r.Layout, context.ResponseWriter(), context)
}
}
// Executes a template by name to be written to the writer with the context
// supplied. This is mainly a convenience method.
func (h *Handler) execTemplate(name string, writer io.Writer, context Context) {
if h.templateStore == nil {
panic("No template store associated with handler")
}
err := h.templateStore.ExecuteTemplate(writer, name, context)
if err != nil {
panic("Template " + name + " could not be executed")
}
}