-
Notifications
You must be signed in to change notification settings - Fork 0
/
html.go
311 lines (271 loc) · 8.77 KB
/
html.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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
package jeen
import (
"bytes"
"fmt"
_html "html/template"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
_text "text/template"
)
type Html struct {
// response writer
writer http.ResponseWriter
// template engine
engine *HtmlEngine
}
// create new html response
func newHtml(rw http.ResponseWriter, e *HtmlEngine) *Html {
return &Html{
writer: rw,
engine: e,
}
}
// Success is shortcut for Response with StatusOK = 200,
// use escape = false if don't need html escape (default `true`)
func (h *Html) Success(filename string, data Map, escape ...bool) error {
return h.Response(http.StatusOK, filename, data, escape...)
}
// Error is shortcut for Response with StatusInternalServerError = 500,
// use escape = false if don't need html escape (default `true`)
func (h *Html) Error(filename string, data Map, escape ...bool) error {
return h.Response(http.StatusInternalServerError, filename, data, escape...)
}
// Timeout is shortcut for Response with StatusGatewayTimeout = 504,
// use escape = false if don't need html escape (default `true`)
func (h *Html) Timeout(filename string, data Map, escape ...bool) error {
return h.Response(http.StatusGatewayTimeout, filename, data, escape...)
}
// Forbidden is shortcut for Response with StatusForbidden = 403,
// use escape = false if don't need html escape (default `true`)
func (h *Html) Forbidden(filename string, data Map, escape ...bool) error {
return h.Response(http.StatusForbidden, filename, data, escape...)
}
// NotFound is shortcut for Response with StatusNotFound = 404,
// use escape = false if don't need html escape (default `true`)
func (h *Html) NotFound(filename string, data Map, escape ...bool) error {
return h.Response(http.StatusNotFound, filename, data, escape...)
}
// Unauthorized is shortcut for Response with StatusUnauthorized = 401,
// use escape = false if don't need html escape (default `true`)
func (h *Html) Unauthorized(filename string, data Map, escape ...bool) error {
return h.Response(http.StatusUnauthorized, filename, data, escape...)
}
// Response render html from filename, output to browser,
// use escape = false if don't need html escape (default `true`)
func (h *Html) Response(statusCode int, filename string, data Map, escape ...bool) error {
return h.engine.Response(h.writer, statusCode, filename, data, len(escape) == 0 || escape[0])
}
// Render render to io.Writer without output to browser,
// example:
//
// var b bytes.Buffer
// err = res.Html.Output(&b, ...)
// fmt.Println(b.String())
func (h *Html) Render(out io.Writer, filename string, data Map, escape ...bool) error {
return h.engine.Render(out, filename, data, len(escape) == 0 || escape[0])
}
// ResponseString response to browser from statuscode and string content
func (h *Html) ResponseString(statusCode int, text string) error {
h.writer.WriteHeader(statusCode)
_, err := h.writer.Write([]byte(text))
return err
}
// ResponseByte render to browser from statuscode and byte content
func (h *Html) ResponseByte(statusCode int, text []byte) error {
h.writer.WriteHeader(statusCode)
_, err := h.writer.Write(text)
return err
}
// StatusText response output to browser, with header and default string from statuscode
func (h *Html) StatusText(statusCode int) error {
return h.ResponseString(statusCode, http.StatusText(statusCode))
}
//
// The following source code was copied from the goview package, we cannot use
// the package directly because goview only supports escaped html.
//
// Source: https://github.com/foolin/goview/blob/master/view.go
//
// engine used to render html or text output
type HtmlEngine struct {
template *Template
tplMapHtml map[string]*_html.Template
tplMapText map[string]*_text.Template
tplMutex sync.RWMutex
}
// create new engine from template config
func newTemplateEngine(template *Template) *HtmlEngine {
return &HtmlEngine{
template: template,
tplMapHtml: make(map[string]*_html.Template),
tplMapText: make(map[string]*_text.Template),
tplMutex: sync.RWMutex{},
}
}
// Response render output to responseWriter
func (e *HtmlEngine) Response(w http.ResponseWriter, statusCode int, name string, data Map, escape bool) error {
header := w.Header()
if val := header["Content-Type"]; len(val) == 0 {
header["Content-Type"] = []string{"text/html; charset=utf-8"}
}
w.WriteHeader(statusCode)
return e.executeRender(w, name, data, escape)
}
// render output to io.Writer, so we can use that output before render to browser
func (e *HtmlEngine) Render(w io.Writer, name string, data Map, escape bool) error {
return e.executeRender(w, name, data, escape)
}
// shortcut to render html
func (e *HtmlEngine) executeRender(out io.Writer, name string, data Map, escape bool) error {
useMaster := true
if filepath.Ext(name) == ".html" {
useMaster = false
name = strings.TrimSuffix(name, ".html")
}
if escape {
return e.htmlEscape(out, name, data, useMaster)
}
return e.htmlString(out, name, data, useMaster)
}
// execute html template with escaped
func (e *HtmlEngine) htmlEscape(out io.Writer, name string, data Map, useMaster bool) error {
var tpl *_html.Template
var err error
var ok bool
allFuncs := make(_html.FuncMap, 0)
allFuncs["include"] = func(layout string) (_html.HTML, error) {
buf := new(bytes.Buffer)
err := e.htmlEscape(buf, layout, data, false)
return _html.HTML(buf.String()), err
}
// Get the plugin collection
for k, v := range e.template.Funcs {
allFuncs[k] = v
}
e.tplMutex.RLock()
tpl, ok = e.tplMapHtml[name]
e.tplMutex.RUnlock()
exeName := name
if useMaster && e.template.Master != "" {
exeName = e.template.Master
}
if !ok || e.template.DisableCache {
tplList := make([]string, 0)
if useMaster {
//render()
if e.template.Master != "" {
tplList = append(tplList, e.template.Master)
}
}
tplList = append(tplList, name)
tplList = append(tplList, e.template.Partials...)
// Loop through each template and test the full path
tpl = _html.New(name).Funcs(allFuncs).Delims(e.template.Delims.Left, e.template.Delims.Right)
for _, v := range tplList {
var data string
data, err = e.fileHandler(e.template, v)
if err != nil {
return err
}
var tmpl *_html.Template
if v == name {
tmpl = tpl
} else {
tmpl = tpl.New(v)
}
_, err = tmpl.Parse(data)
if err != nil {
return fmt.Errorf("ViewEngine render parser name:%v, error: %v", v, err)
}
}
e.tplMutex.Lock()
e.tplMapHtml[name] = tpl
e.tplMutex.Unlock()
}
// Display the content to the screen
err = tpl.Funcs(allFuncs).ExecuteTemplate(out, exeName, data)
if err != nil {
return fmt.Errorf("ViewEngine execute template error: %v", err)
}
return nil
}
// execute html template without escaped
func (e *HtmlEngine) htmlString(out io.Writer, name string, data Map, useMaster bool) error {
var tpl *_text.Template
var err error
var ok bool
allFuncs := make(_text.FuncMap, 0)
allFuncs["include"] = func(layout string) (string, error) {
buf := new(bytes.Buffer)
err := e.htmlString(buf, layout, data, false)
return buf.String(), err
}
// Get the plugin collection
for k, v := range e.template.Funcs {
allFuncs[k] = v
}
e.tplMutex.RLock()
tpl, ok = e.tplMapText[name]
e.tplMutex.RUnlock()
exeName := name
if useMaster && e.template.Master != "" {
exeName = e.template.Master
}
if !ok || e.template.DisableCache {
tplList := make([]string, 0)
if useMaster {
//render()
if e.template.Master != "" {
tplList = append(tplList, e.template.Master)
}
}
tplList = append(tplList, name)
tplList = append(tplList, e.template.Partials...)
// Loop through each template and test the full path
tpl = _text.New(name).Funcs(allFuncs).Delims(e.template.Delims.Left, e.template.Delims.Right)
for _, v := range tplList {
var data string
data, err = e.fileHandler(e.template, v)
if err != nil {
return err
}
var tmpl *_text.Template
if v == name {
tmpl = tpl
} else {
tmpl = tpl.New(v)
}
_, err = tmpl.Parse(data)
if err != nil {
return fmt.Errorf("ViewEngine render parser name:%v, error: %v", v, err)
}
}
e.tplMutex.Lock()
e.tplMapText[name] = tpl
e.tplMutex.Unlock()
}
// Display the content to the screen
err = tpl.Funcs(allFuncs).ExecuteTemplate(out, exeName, data)
if err != nil {
return fmt.Errorf("ViewEngine execute template error: %v", err)
}
return nil
}
// filehandler
func (e *HtmlEngine) fileHandler(config *Template, tplFile string) (content string, err error) {
// Get the absolute path of the root template
path, err := filepath.Abs(config.Root + string(os.PathSeparator) + tplFile + ".html")
if err != nil {
return "", fmt.Errorf("ViewEngine path:%v error: %v", path, err)
}
data, err := ioutil.ReadFile(path)
if err != nil {
return "", fmt.Errorf("ViewEngine render read name:%v, path:%v, error: %v", tplFile, path, err)
}
return string(data), nil
}