@marcel91200 / caddy Mirror · read-only / commits / ca4fae64
Fork 0

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