-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhandler.go
More file actions
163 lines (140 loc) · 4.74 KB
/
handler.go
File metadata and controls
163 lines (140 loc) · 4.74 KB
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
package templ
import (
"net/http"
)
// ComponentHandler is a http.Handler that renders components.
type ComponentHandler struct {
Component Component
Status int
ContentType string
ErrorHandler func(r *http.Request, err error) http.Handler
StreamResponse bool
FragmentIDs []any
}
const componentHandlerErrorMessage = "templ: failed to render template"
func (ch *ComponentHandler) handleRenderErr(w http.ResponseWriter, r *http.Request, err error) {
if ch.ErrorHandler != nil {
w.Header().Set("Content-Type", ch.ContentType)
ch.ErrorHandler(r, err).ServeHTTP(w, r)
return
}
http.Error(w, componentHandlerErrorMessage, http.StatusInternalServerError)
}
func (ch *ComponentHandler) ServeHTTPBufferedFragment(w http.ResponseWriter, r *http.Request) {
// Since the component may error, write to a buffer first.
// This prevents partial responses from being written to the client.
buf := GetBuffer()
defer ReleaseBuffer(buf)
// Render the component into io.Discard, but use the buffer for fragments.
if err := RenderFragments(r.Context(), buf, ch.Component, ch.FragmentIDs...); err != nil {
ch.handleRenderErr(w, r, err)
return
}
// The component rendered successfully, we can write the Content-Type and Status.
w.Header().Set("Content-Type", ch.ContentType)
if ch.Status != 0 {
w.WriteHeader(ch.Status)
}
// Ignore write error like http.Error() does, because there is
// no way to recover at this point.
_, _ = w.Write(buf.Bytes())
}
func (ch *ComponentHandler) ServeHTTPBufferedComplete(w http.ResponseWriter, r *http.Request) {
// Since the component may error, write to a buffer first.
// This prevents partial responses from being written to the client.
buf := GetBuffer()
defer ReleaseBuffer(buf)
// Render the component into the buffer.
if err := ch.Component.Render(r.Context(), buf); err != nil {
ch.handleRenderErr(w, r, err)
return
}
// The component rendered successfully, we can write the Content-Type and Status.
w.Header().Set("Content-Type", ch.ContentType)
if ch.Status != 0 {
w.WriteHeader(ch.Status)
}
// Ignore write error like http.Error() does, because there is
// no way to recover at this point.
_, _ = w.Write(buf.Bytes())
}
func (ch *ComponentHandler) ServeHTTPBuffered(w http.ResponseWriter, r *http.Request) {
// If fragments are specified, render only those.
if len(ch.FragmentIDs) > 0 {
ch.ServeHTTPBufferedFragment(w, r)
return
}
// Otherwise, render the complete component.
ch.ServeHTTPBufferedComplete(w, r)
}
func (ch *ComponentHandler) ServeHTTPStreamed(w http.ResponseWriter, r *http.Request) {
// If streaming, we do not buffer the response, so set the headers immediately.
w.Header().Set("Content-Type", ch.ContentType)
if ch.Status != 0 {
w.WriteHeader(ch.Status)
}
// Pass fragment names to the context if specified.
if len(ch.FragmentIDs) > 0 {
// Render the component into io.Discard, but use the buffer for fragments.
if err := RenderFragments(r.Context(), w, ch.Component, ch.FragmentIDs...); err != nil {
ch.handleRenderErr(w, r, err)
return
}
return
}
// Render the component into the buffer.
if err := ch.Component.Render(r.Context(), w); err != nil {
ch.handleRenderErr(w, r, err)
return
}
}
// ServeHTTP implements the http.Handler interface.
func (ch ComponentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if ch.StreamResponse {
ch.ServeHTTPStreamed(w, r)
return
}
ch.ServeHTTPBuffered(w, r)
}
// Handler creates a http.Handler that renders the template.
func Handler(c Component, options ...func(*ComponentHandler)) *ComponentHandler {
ch := &ComponentHandler{
Component: c,
ContentType: "text/html; charset=utf-8",
}
for _, o := range options {
o(ch)
}
return ch
}
// WithStatus sets the HTTP status code returned by the ComponentHandler.
func WithStatus(status int) func(*ComponentHandler) {
return func(ch *ComponentHandler) {
ch.Status = status
}
}
// WithContentType sets the Content-Type header returned by the ComponentHandler.
func WithContentType(contentType string) func(*ComponentHandler) {
return func(ch *ComponentHandler) {
ch.ContentType = contentType
}
}
// WithErrorHandler sets the error handler used if rendering fails.
func WithErrorHandler(eh func(r *http.Request, err error) http.Handler) func(*ComponentHandler) {
return func(ch *ComponentHandler) {
ch.ErrorHandler = eh
}
}
// WithStreaming sets the ComponentHandler to stream the response instead of buffering it.
func WithStreaming() func(*ComponentHandler) {
return func(ch *ComponentHandler) {
ch.StreamResponse = true
}
}
// WithFragments sets the ids of the fragments to render.
// If not set, all content is rendered.
func WithFragments(ids ...any) func(*ComponentHandler) {
return func(ch *ComponentHandler) {
ch.FragmentIDs = ids
}
}