-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathehtml.go
147 lines (118 loc) · 3.57 KB
/
ehtml.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
// Copyright (c) 2020, Mohlmann Solutions SRL. All rights reserved.
// Use of this source code is governed by a License that can be found in the LICENSE file.
// SPDX-License-Identifier: BSD-3-Clause
package ehtml
import (
"bytes"
"fmt"
"html/template"
"net/http"
"strconv"
"sync"
)
// Status holds an HTTP status code
type Status int
// String returns the text descriptiom for the HTTP status code.
// It returns the empty string if the code is unknown.
func (s Status) String() string { return http.StatusText(int(s)) }
// Int returns Status as int
func (s Status) Int() int { return int(s) }
func (s Status) toA() string { return strconv.Itoa(s.Int()) }
// Provider of data to templates
type Provider interface {
// Request returns the incomming http Request object
Request() *http.Request
Status() Status
Message() string
// String returns the status code, status text and message in a single string.
// For example: "400 Bad Request: Parsing form data"
String() string
}
// Data can be used as a default or embedded type to implement Provider.
type Data struct {
Req *http.Request
Code Status
Msg string
}
// Request implements Provider
func (d *Data) Request() *http.Request { return d.Req }
// Status implements Provider
func (d *Data) Status() Status { return d.Code }
// Message implements Provider
func (d *Data) Message() string { return d.Msg }
func (d *Data) String() string {
return fmt.Sprintf("%d %s: %s", d.Code, d.Code, d.Msg)
}
// DefaultTmpl is a placeholder template for `Pages.Render()`
const DefaultTmpl = `{{ define "error" -}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{ .String }}</title>
</head>
<body>
<h1>{{ .Status.Int }} {{ .Status }}</h1>
<p>{{ .Message }}</p>
</body>
</html>
{{- end -}}
`
var defTmpl = template.Must(template.New("error").Parse(DefaultTmpl))
// Pages allows setting of status page templates.
// Whenever such page needs to be served, a Lookup is done for a template
// named by the code. Eg: "404".
// A generic template named "error" can be provided
// and will be used if there is no status-specific template defined.
//
// If Tmpl is `nil` or no templates are found using above Lookup scheme,
// `DefaultErrTmpl` will be used.
type Pages struct {
Tmpl *template.Template
}
func (p *Pages) template(s Status) *template.Template {
if p.Tmpl == nil {
return defTmpl
}
if tmpl := p.Tmpl.Lookup(s.toA()); tmpl != nil {
return tmpl
}
if tmpl := p.Tmpl.Lookup("error"); tmpl != nil {
return tmpl
}
return defTmpl
}
type bufPool struct {
p sync.Pool
}
func (p *bufPool) Get() *bytes.Buffer {
if b, ok := p.p.Get().(*bytes.Buffer); ok {
return b
}
return new(bytes.Buffer)
}
func (p *bufPool) Put(b *bytes.Buffer) {
b.Reset()
p.p.Put(b)
}
var buffers = &bufPool{}
// RenderError is returned to the client if the template failed to render.
// This doesn't look nice, but it prevents partial responses.
const RenderError = "500 Internal server error. While handling:\n%s"
// Render a page for passed status code.
// In case of template execution errors,
// "RenderError" including the original status and message is sent to the client.
func (p *Pages) Render(w http.ResponseWriter, dp Provider) error {
buf := buffers.Get()
defer buffers.Put(buf)
if err := p.template(dp.Status()).Execute(buf, dp); err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, RenderError, dp)
return fmt.Errorf("ehtml Render template: %w", err)
}
w.WriteHeader(dp.Status().Int())
if _, err := buf.WriteTo(w); err != nil {
return fmt.Errorf("ehtml Render, write to client: %w", err)
}
return nil
}