-
Notifications
You must be signed in to change notification settings - Fork 0
/
fragments.go
149 lines (125 loc) · 3.85 KB
/
fragments.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
package uos
import (
"io"
"net/http"
)
// FragmentSpec describes an interface a web application fragment must provide.
type FragmentSpec interface {
// Name returns the short name of a fragment. The fragment is available at '/fragments/<name>'
// and uses the template 'fragment_<name>'.
Name() string
}
// FragmentSpecRead describes an interface a fragment that supports GET requests must provide.
type FragmentSpecRead interface {
// GetContextObject returns the fragment template context object for the given URL parameters.
GetContextObject(params Getter) (interface{}, error)
}
// FragmentHandler returns a handler for the "/fragments/" route providing the specified fragments.
// Creates default handler for fragments (as defined in templates directory).
// The handler can be activated using RegisterAppRequestHandlers.
func FragmentHandler(fragments ...FragmentSpec) AppRequestHandlerMapping {
return AppRequestHandlerMapping{
Route: "/fragments/",
Handler: getFragmentWebHandlerFunc(fragments),
Options: AppRequestHandlerOptions{
NoSitemap: true,
},
}
}
var fragmentRegistry map[string]FragmentSpec
func getFragmentWebHandlerFunc(fragments []FragmentSpec) AppRequestHandler {
if fragmentRegistry != nil {
Log.Panic("multiple fragment handler registration")
}
fragmentRegistry = map[string]FragmentSpec{}
for _, f := range fragments {
fragmentRegistry[f.Name()] = f
Log.DebugContext("register fragment spec", LogContext{"name": f.Name()})
}
return func(w http.ResponseWriter, r *http.Request) {
// determine fragment
name := getElementName("fragments", r.URL.Path)
Log.DebugContextR(
r, "handle fragment",
LogContext{
"name": name,
"method": r.Method,
},
)
// only GET requests are supported
if r.Method != http.MethodGet {
RespondNotImplemented(w)
return
}
// process request
status, err := handleFragment(w, r, name, r.Form)
if err != nil {
handleFragmentError(w, r, "could not handle fragment", err)
return
}
if status != http.StatusOK {
respondWithStatusText(w, status)
}
}
}
func handleFragment(w io.Writer, r *http.Request, name string, form Getter) (int, error) {
// get fragment specification
fragmentSpec, ok := fragmentRegistry[name]
if !ok {
// might be fragment without specification -> directly forward to rendering
Log.DebugContextR(r, "handle fragment without spec", LogContext{"name": name})
return renderObjectFragment(w, r, name, form.Get("p"))
}
// does the fragment support GET method?
fragmentRead, ok := fragmentSpec.(FragmentSpecRead)
if !ok {
return http.StatusNotImplemented, nil
}
// process fragment
obj, err := fragmentRead.GetContextObject(form)
if err != nil {
return -1, err
}
return renderObjectFragment(w, r, name, obj)
}
func handleFragmentError(w http.ResponseWriter, r *http.Request, message string, err error) {
switch err {
case ErrorFragmentNotFound:
RespondNotFound(w)
return
case ErrorFragmentInvalidRequest:
RespondBadRequest(w)
return
}
// all other cases: log as internal error
Log.ErrorObjR(r, message, err)
RespondInternalServerError(w)
}
func renderObjectFragment(w io.Writer, r *http.Request, name string, obj interface{}) (int, error) {
data := map[string]interface{}{}
if obj != nil {
data["Object"] = obj
}
return renderFragment(w, r, name, data)
}
func renderFragment(w io.Writer, r *http.Request, name string, data map[string]interface{}) (int, error) {
fragmentTemplateName := "fragment_" + name
// initialize template
tmpl, status := loadTemplate(w, r, name, fragmentTemplateName)
if tmpl == nil {
return status, nil
}
// render fragment
err := tmpl.ExecuteTemplate(w, name, data)
if err != nil {
Log.ErrorContextR(
r, "could not execute fragment template",
LogContext{
"fragment": name,
"error": err,
},
)
return http.StatusInternalServerError, nil
}
return http.StatusOK, nil
}