Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

middleware: add Discard method to WrapResponseWriter #926

Merged
merged 5 commits into from
Jun 28, 2024

Conversation

patrislav
Copy link
Contributor

The Discard method will cause the WrapResponseWriter to only write to the tee'd writer, while the original http.ResponseWriter would be untouched.

This is useful in situations where the headers or the buffer need to be modified in a middleware after the handler returns. One concrete example is response body signing, e.g.:

func SigningMiddleware(next http.Handler) http.handler {
    return http.HandlerFunc(w http.ResponseWriter, r *http.Request) {
        var buf bytes.Buffer
        ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
        ww.Tee(&buf)
        ww.Discard() // discard writes to the original response writer
        
        next.ServeHTTP(ww, r)

        signature := calculateBodySignature(buf.Bytes())
        w.Header().Set("signature", signature)
        buf.WriteTo(w)
    }
}

Copy link
Contributor

@VojtechVitek VojtechVitek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🎉

Minor feedback.

middleware/wrap_writer.go Outdated Show resolved Hide resolved
middleware/wrap_writer_test.go Show resolved Hide resolved
@@ -61,6 +61,9 @@ type WrapResponseWriter interface {
Tee(io.Writer)
// Unwrap returns the original proxied target.
Unwrap() http.ResponseWriter
// Discard causes all writes to the original ResponseWriter be discarded,
// instead writing only to the tee'd writer if it's set.
Discard()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is technically a breaking API change -- if there's anyone who implemented this interface externally. But I can't think of any use case why would anyone do that. And it's an easy fix.

So, I'm OK with this change if we bump minor version only. I just wanted to call it out for visibility.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking of this some more, I think we could achieve backward-compatibility by

  1. Creating a new private interface wrapResponseWriter that extends the original interface with the new Discard() method.
    type wrapResponseWriter interface {
         WrapResponseWriter
        
         // Discard causes all writes to the original ResponseWriter be discarded instead writing only to the tee'd writer if it's set.
         Discard()
    }
  2. Returning it from NewWrapResponseWriter() constructor
    - func NewWrapResponseWriter(w http.ResponseWriter, protoMajor int) WrapResponseWriter {
    + func NewWrapResponseWriter(w http.ResponseWriter, protoMajor int) wrapResponseWriter {

I don't see a reason why we'd need to return a public interface in this case. I don't expect anyone re-implementing wrapResponseWriter externally?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I suppose the issue is not people implementing the interface (as they're free to do that) but rather using it to type variables or function arguments. IMO in that case the correct way would rather be to use the http.ResponseWriter or define a custom interface with the methods they need.

Good idea with the private interface, wouldn't it be confusing with the now two similarly named interfaces though?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: We decided to update the original interface, as we don't expect anyone using it directly for anything.

Copy link
Contributor

@VojtechVitek VojtechVitek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@VojtechVitek VojtechVitek merged commit 67be7d9 into go-chi:master Jun 28, 2024
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants