// SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors package echo import ( "bufio" "errors" "net" "net/http" ) // Response wraps an http.ResponseWriter and implements its interface to be used // by an HTTP handler to construct an HTTP response. // See: https://golang.org/pkg/net/http/#ResponseWriter type Response struct { Writer http.ResponseWriter echo *Echo beforeFuncs []func() afterFuncs []func() Status int Size int64 Committed bool } // NewResponse creates a new instance of Response. func NewResponse(w http.ResponseWriter, e *Echo) (r *Response) { return &Response{Writer: w, echo: e} } // Header returns the header map for the writer that will be sent by // WriteHeader. Changing the header after a call to WriteHeader (or Write) has // no effect unless the modified headers were declared as trailers by setting // the "Trailer" header before the call to WriteHeader (see example) // To suppress implicit response headers, set their value to nil. // Example: https://golang.org/pkg/net/http/#example_ResponseWriter_trailers func (r *Response) Header() http.Header { return r.Writer.Header() } // Before registers a function which is called just before the response is written. func (r *Response) Before(fn func()) { r.beforeFuncs = append(r.beforeFuncs, fn) } // After registers a function which is called just after the response is written. // If the `Content-Length` is unknown, none of the after function is executed. func (r *Response) After(fn func()) { r.afterFuncs = append(r.afterFuncs, fn) } // WriteHeader sends an HTTP response header with status code. If WriteHeader is // not called explicitly, the first call to Write will trigger an implicit // WriteHeader(http.StatusOK). Thus explicit calls to WriteHeader are mainly // used to send error codes. func (r *Response) WriteHeader(code int) { if r.Committed { r.echo.Logger.Warn("response already committed") return } r.Status = code for _, fn := range r.beforeFuncs { fn() } r.Writer.WriteHeader(r.Status) r.Committed = true } // Write writes the data to the connection as part of an HTTP reply. func (r *Response) Write(b []byte) (n int, err error) { if !r.Committed { if r.Status == 0 { r.Status = http.StatusOK } r.WriteHeader(r.Status) } n, err = r.Writer.Write(b) r.Size += int64(n) for _, fn := range r.afterFuncs { fn() } return } // Flush implements the http.Flusher interface to allow an HTTP handler to flush // buffered data to the client. // See [http.Flusher](https://golang.org/pkg/net/http/#Flusher) func (r *Response) Flush() { err := http.NewResponseController(r.Writer).Flush() if err != nil && errors.Is(err, http.ErrNotSupported) { panic(errors.New("response writer flushing is not supported")) } } // Hijack implements the http.Hijacker interface to allow an HTTP handler to // take over the connection. // See [http.Hijacker](https://golang.org/pkg/net/http/#Hijacker) func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) { return http.NewResponseController(r.Writer).Hijack() } // Unwrap returns the original http.ResponseWriter. // ResponseController can be used to access the original http.ResponseWriter. // See [https://go.dev/blog/go1.20] func (r *Response) Unwrap() http.ResponseWriter { return r.Writer } func (r *Response) reset(w http.ResponseWriter) { r.beforeFuncs = nil r.afterFuncs = nil r.Writer = w r.Size = 0 r.Status = http.StatusOK r.Committed = false }