Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 13, 14:15 UTC
caddyhttp: Support `respond` with HTTP 103 Early Hints (#5006)
* caddyhttp: Support sending HTTP 103 Early Hints This adds support for early hints in the static_response handler. * caddyhttp: Don't record 1xx responses
Matt Holt committed
Sep 5, 2022 at 13:50 UTC
ca4fae64d99a63291a91e59af5a1e8eef8c8e2d8
4 files changed
+77
-26
modules/caddyhttp/matchers.go
+16
@@ -1121,6 +1121,22 @@ func (m MatchProtocol) Match(r *http.Request) bool {
1121
return r.TLS != nil
1122
case "http":
1123
return r.TLS == nil
1124
+ case "http/1.0":
1125
+ return r.ProtoMajor == 1 && r.ProtoMinor == 0
1126
+ case "http/1.0+":
1127
+ return r.ProtoAtLeast(1, 0)
1128
+ case "http/1.1":
1129
+ return r.ProtoMajor == 1 && r.ProtoMinor == 1
1130
+ case "http/1.1+":
1131
+ return r.ProtoAtLeast(1, 1)
1132
+ case "http/2":
1133
+ return r.ProtoMajor == 2
1134
+ case "http/2+":
1135
+ return r.ProtoAtLeast(2, 0)
1136
+ case "http/3":
1137
+ return r.ProtoMajor == 3
1138
+ case "http/3+":
1139
+ return r.ProtoAtLeast(3, 0)
1140
}
1141
return false
1142
}
modules/caddyhttp/push/handler.go
+17
-3
@@ -29,10 +29,24 @@ func init() {
29
caddy.RegisterModule(Handler{})
30
}
31
32
-// Handler is a middleware for manipulating the request body.
32
+// Handler is a middleware for HTTP/2 server push. Note that
33
+// HTTP/2 server push has been deprecated by some clients and
34
+// its use is discouraged unless you can accurately predict
35
+// which resources actually need to be pushed to the client;
36
+// it can be difficult to know what the client already has
37
+// cached. Pushing unnecessary resources results in worse
38
+// performance. Consider using HTTP 103 Early Hints instead.
39
+//
40
+// This handler supports pushing from Link headers; in other
41
+// words, if the eventual response has Link headers, this
42
+// handler will push the resources indicated by those headers,
43
+// even without specifying any resources in its config.
44
type Handler struct {
34
- Resources []Resource `json:"resources,omitempty"`
35
- Headers *HeaderConfig `json:"headers,omitempty"`
45
+ // The resources to push.
46
+ Resources []Resource `json:"resources,omitempty"`
47
+
48
+ // Headers to modify for the push requests.
49
+ Headers *HeaderConfig `json:"headers,omitempty"`
50
51
logger *zap.Logger
52
}
modules/caddyhttp/responsewriter.go
+31
-21
@@ -111,15 +111,15 @@ type responseRecorder struct {
111
//
112
// Proper usage of a recorder looks like this:
113
//
114
-// rec := caddyhttp.NewResponseRecorder(w, buf, shouldBuffer)
115
-// err := next.ServeHTTP(rec, req)
116
-// if err != nil {
117
-// return err
118
-// }
119
-// if !rec.Buffered() {
120
-// return nil
121
-// }
122
-// // process the buffered response here
114
+// rec := caddyhttp.NewResponseRecorder(w, buf, shouldBuffer)
115
+// err := next.ServeHTTP(rec, req)
116
+// if err != nil {
117
+// return err
118
+// }
119
+// if !rec.Buffered() {
120
+// return nil
121
+// }
122
+// // process the buffered response here
123
//
124
// The header map is not buffered; i.e. the ResponseRecorder's Header()
125
// method returns the same header map of the underlying ResponseWriter.
@@ -129,7 +129,7 @@ type responseRecorder struct {
129
// Once you are ready to write the response, there are two ways you can
130
// do it. The easier way is to have the recorder do it:
131
//
132
-// rec.WriteResponse()
132
+// rec.WriteResponse()
133
//
134
// This writes the recorded response headers as well as the buffered body.
135
// Or, you may wish to do it yourself, especially if you manipulated the
@@ -138,9 +138,12 @@ type responseRecorder struct {
138
// recorder's body buffer, but you might have your own body to write
139
// instead):
140
//
141
-// w.WriteHeader(rec.Status())
142
-// io.Copy(w, rec.Buffer())
141
+// w.WriteHeader(rec.Status())
142
+// io.Copy(w, rec.Buffer())
143
//
144
+// As a special case, 1xx responses are not buffered nor recorded
145
+// because they are not the final response; they are passed through
146
+// directly to the underlying ResponseWriter.
147
func NewResponseRecorder(w http.ResponseWriter, buf *bytes.Buffer, shouldBuffer ShouldBufferFunc) ResponseRecorder {
148
return &responseRecorder{
149
ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: w},
@@ -149,22 +152,29 @@ func NewResponseRecorder(w http.ResponseWriter, buf *bytes.Buffer, shouldBuffer
152
}
153
}
154
155
+// WriteHeader writes the headers with statusCode to the wrapped
156
+// ResponseWriter unless the response is to be buffered instead.
157
+// 1xx responses are never buffered.
158
func (rr *responseRecorder) WriteHeader(statusCode int) {
159
if rr.wroteHeader {
160
return
161
}
156
- rr.statusCode = statusCode
157
- rr.wroteHeader = true
162
159
- // decide whether we should buffer the response
160
- if rr.shouldBuffer == nil {
161
- rr.stream = true
162
- } else {
163
- rr.stream = !rr.shouldBuffer(rr.statusCode, rr.ResponseWriterWrapper.Header())
163
+ // 1xx responses aren't final; just informational
164
+ if statusCode < 100 || statusCode > 199 {
165
+ rr.statusCode = statusCode
166
+ rr.wroteHeader = true
167
+
168
+ // decide whether we should buffer the response
169
+ if rr.shouldBuffer == nil {
170
+ rr.stream = true
171
+ } else {
172
+ rr.stream = !rr.shouldBuffer(rr.statusCode, rr.ResponseWriterWrapper.Header())
173
+ }
174
}
175
166
- // if not buffered, immediately write header
167
- if rr.stream {
176
+ // if informational or not buffered, immediately write header
177
+ if rr.stream || (100 <= statusCode && statusCode <= 199) {
178
rr.ResponseWriterWrapper.WriteHeader(rr.statusCode)
179
}
180
}
modules/caddyhttp/staticresp.go
+13
-2
@@ -86,6 +86,12 @@ Response headers may be added using the --header flag for each header field.
86
type StaticResponse struct {
87
// The HTTP status code to respond with. Can be an integer or,
88
// if needing to use a placeholder, a string.
89
+ //
90
+ // If the status code is 103 (Early Hints), the response headers
91
+ // will be written to the client immediately, the body will be
92
+ // ignored, and the next handler will be invoked. This behavior
93
+ // is EXPERIMENTAL while RFC 8297 is a draft, and may be changed
94
+ // or removed.
95
StatusCode WeakString `json:"status_code,omitempty"`
96
97
// Header fields to set on the response; overwrites any existing
@@ -170,7 +176,7 @@ func (s *StaticResponse) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
176
return nil
177
}
178
173
-func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
179
+func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error {
180
// close the connection immediately
181
if s.Abort {
182
panic(http.ErrAbortHandler)
@@ -237,10 +243,15 @@ func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Hand
243
w.WriteHeader(statusCode)
244
245
// write response body
240
- if body != "" {
246
+ if statusCode != http.StatusEarlyHints && body != "" {
247
fmt.Fprint(w, body)
248
}
249
250
+ // continue handling after Early Hints as they are not the final response
251
+ if statusCode == http.StatusEarlyHints {
252
+ return next.ServeHTTP(w, r)
253
+ }
254
+
255
return nil
256
}
257