mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-26 13:43:47 +03:00
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
This commit is contained in:
parent
ad69503aef
commit
ca4fae64d9
4 changed files with 77 additions and 26 deletions
|
@ -1121,6 +1121,22 @@ func (m MatchProtocol) Match(r *http.Request) bool {
|
|||
return r.TLS != nil
|
||||
case "http":
|
||||
return r.TLS == nil
|
||||
case "http/1.0":
|
||||
return r.ProtoMajor == 1 && r.ProtoMinor == 0
|
||||
case "http/1.0+":
|
||||
return r.ProtoAtLeast(1, 0)
|
||||
case "http/1.1":
|
||||
return r.ProtoMajor == 1 && r.ProtoMinor == 1
|
||||
case "http/1.1+":
|
||||
return r.ProtoAtLeast(1, 1)
|
||||
case "http/2":
|
||||
return r.ProtoMajor == 2
|
||||
case "http/2+":
|
||||
return r.ProtoAtLeast(2, 0)
|
||||
case "http/3":
|
||||
return r.ProtoMajor == 3
|
||||
case "http/3+":
|
||||
return r.ProtoAtLeast(3, 0)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -29,10 +29,24 @@ func init() {
|
|||
caddy.RegisterModule(Handler{})
|
||||
}
|
||||
|
||||
// Handler is a middleware for manipulating the request body.
|
||||
// Handler is a middleware for HTTP/2 server push. Note that
|
||||
// HTTP/2 server push has been deprecated by some clients and
|
||||
// its use is discouraged unless you can accurately predict
|
||||
// which resources actually need to be pushed to the client;
|
||||
// it can be difficult to know what the client already has
|
||||
// cached. Pushing unnecessary resources results in worse
|
||||
// performance. Consider using HTTP 103 Early Hints instead.
|
||||
//
|
||||
// This handler supports pushing from Link headers; in other
|
||||
// words, if the eventual response has Link headers, this
|
||||
// handler will push the resources indicated by those headers,
|
||||
// even without specifying any resources in its config.
|
||||
type Handler struct {
|
||||
Resources []Resource `json:"resources,omitempty"`
|
||||
Headers *HeaderConfig `json:"headers,omitempty"`
|
||||
// The resources to push.
|
||||
Resources []Resource `json:"resources,omitempty"`
|
||||
|
||||
// Headers to modify for the push requests.
|
||||
Headers *HeaderConfig `json:"headers,omitempty"`
|
||||
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
|
|
@ -111,15 +111,15 @@ type responseRecorder struct {
|
|||
//
|
||||
// Proper usage of a recorder looks like this:
|
||||
//
|
||||
// rec := caddyhttp.NewResponseRecorder(w, buf, shouldBuffer)
|
||||
// err := next.ServeHTTP(rec, req)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if !rec.Buffered() {
|
||||
// return nil
|
||||
// }
|
||||
// // process the buffered response here
|
||||
// rec := caddyhttp.NewResponseRecorder(w, buf, shouldBuffer)
|
||||
// err := next.ServeHTTP(rec, req)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if !rec.Buffered() {
|
||||
// return nil
|
||||
// }
|
||||
// // process the buffered response here
|
||||
//
|
||||
// The header map is not buffered; i.e. the ResponseRecorder's Header()
|
||||
// method returns the same header map of the underlying ResponseWriter.
|
||||
|
@ -129,7 +129,7 @@ type responseRecorder struct {
|
|||
// Once you are ready to write the response, there are two ways you can
|
||||
// do it. The easier way is to have the recorder do it:
|
||||
//
|
||||
// rec.WriteResponse()
|
||||
// rec.WriteResponse()
|
||||
//
|
||||
// This writes the recorded response headers as well as the buffered body.
|
||||
// Or, you may wish to do it yourself, especially if you manipulated the
|
||||
|
@ -138,9 +138,12 @@ type responseRecorder struct {
|
|||
// recorder's body buffer, but you might have your own body to write
|
||||
// instead):
|
||||
//
|
||||
// w.WriteHeader(rec.Status())
|
||||
// io.Copy(w, rec.Buffer())
|
||||
// w.WriteHeader(rec.Status())
|
||||
// io.Copy(w, rec.Buffer())
|
||||
//
|
||||
// As a special case, 1xx responses are not buffered nor recorded
|
||||
// because they are not the final response; they are passed through
|
||||
// directly to the underlying ResponseWriter.
|
||||
func NewResponseRecorder(w http.ResponseWriter, buf *bytes.Buffer, shouldBuffer ShouldBufferFunc) ResponseRecorder {
|
||||
return &responseRecorder{
|
||||
ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: w},
|
||||
|
@ -149,22 +152,29 @@ func NewResponseRecorder(w http.ResponseWriter, buf *bytes.Buffer, shouldBuffer
|
|||
}
|
||||
}
|
||||
|
||||
// WriteHeader writes the headers with statusCode to the wrapped
|
||||
// ResponseWriter unless the response is to be buffered instead.
|
||||
// 1xx responses are never buffered.
|
||||
func (rr *responseRecorder) WriteHeader(statusCode int) {
|
||||
if rr.wroteHeader {
|
||||
return
|
||||
}
|
||||
rr.statusCode = statusCode
|
||||
rr.wroteHeader = true
|
||||
|
||||
// decide whether we should buffer the response
|
||||
if rr.shouldBuffer == nil {
|
||||
rr.stream = true
|
||||
} else {
|
||||
rr.stream = !rr.shouldBuffer(rr.statusCode, rr.ResponseWriterWrapper.Header())
|
||||
// 1xx responses aren't final; just informational
|
||||
if statusCode < 100 || statusCode > 199 {
|
||||
rr.statusCode = statusCode
|
||||
rr.wroteHeader = true
|
||||
|
||||
// decide whether we should buffer the response
|
||||
if rr.shouldBuffer == nil {
|
||||
rr.stream = true
|
||||
} else {
|
||||
rr.stream = !rr.shouldBuffer(rr.statusCode, rr.ResponseWriterWrapper.Header())
|
||||
}
|
||||
}
|
||||
|
||||
// if not buffered, immediately write header
|
||||
if rr.stream {
|
||||
// if informational or not buffered, immediately write header
|
||||
if rr.stream || (100 <= statusCode && statusCode <= 199) {
|
||||
rr.ResponseWriterWrapper.WriteHeader(rr.statusCode)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,6 +86,12 @@ Response headers may be added using the --header flag for each header field.
|
|||
type StaticResponse struct {
|
||||
// The HTTP status code to respond with. Can be an integer or,
|
||||
// if needing to use a placeholder, a string.
|
||||
//
|
||||
// If the status code is 103 (Early Hints), the response headers
|
||||
// will be written to the client immediately, the body will be
|
||||
// ignored, and the next handler will be invoked. This behavior
|
||||
// is EXPERIMENTAL while RFC 8297 is a draft, and may be changed
|
||||
// or removed.
|
||||
StatusCode WeakString `json:"status_code,omitempty"`
|
||||
|
||||
// Header fields to set on the response; overwrites any existing
|
||||
|
@ -170,7 +176,7 @@ func (s *StaticResponse) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
|
||||
func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error {
|
||||
// close the connection immediately
|
||||
if s.Abort {
|
||||
panic(http.ErrAbortHandler)
|
||||
|
@ -237,10 +243,15 @@ func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Hand
|
|||
w.WriteHeader(statusCode)
|
||||
|
||||
// write response body
|
||||
if body != "" {
|
||||
if statusCode != http.StatusEarlyHints && body != "" {
|
||||
fmt.Fprint(w, body)
|
||||
}
|
||||
|
||||
// continue handling after Early Hints as they are not the final response
|
||||
if statusCode == http.StatusEarlyHints {
|
||||
return next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue