From 4c3dc07053b73428f5ea039470d8f1b17edcfbd7 Mon Sep 17 00:00:00 2001 From: Carlo Alberto Ferraris Date: Tue, 26 Jan 2021 14:59:54 +0900 Subject: [PATCH 1/4] implement optional interfaces dynamically --- adapter.go | 6 +-- optional.go | 113 +++++++++++++++++++++++++++++++++++++++++++++ response_writer.go | 26 ----------- 3 files changed, 114 insertions(+), 31 deletions(-) create mode 100644 optional.go diff --git a/adapter.go b/adapter.go index c311d07..b1c2e46 100644 --- a/adapter.go +++ b/adapter.go @@ -90,11 +90,7 @@ func Adapter(opts ...Option) (func(http.Handler) http.Handler, error) { writerPool.Put(gw) }() - if _, ok := w.(http.CloseNotifier); ok { - w = compressWriterWithCloseNotify{gw} - } else { - w = gw - } + w = extend(gw) h.ServeHTTP(w, r) }) diff --git a/optional.go b/optional.go new file mode 100644 index 0000000..5d300de --- /dev/null +++ b/optional.go @@ -0,0 +1,113 @@ +package httpcompression + +import ( + "net/http" +) + +// extend returns a http.ResponseWriter that wraps the compressWriter and that +// dynamically exposes some optional interfaces of http.ResponseWriter. +// Currently the supported optional interfaces are http.Hijacker, http.Pusher, +// and http.CloseNotifier. +func extend(cw *compressWriter) http.ResponseWriter { + switch r := cw.ResponseWriter.(type) { + case iHijackPushCloseNotifier: + return cwHijackPushCloseNotifier{cw, r} + case iPushCloseNotifier: + return cwPushCloseNotifier{cw, r} + case iHijackPusher: + return cwHijackPusher{cw, r} + case iHijackCloseNotifier: + return cwHijackCloseNotifier{cw, r} + case http.CloseNotifier: + return cwCloseNotifier{cw, r} + case http.Hijacker: + return cwHijacker{cw, r} + case http.Pusher: + return cwPusher{cw, r} + default: + return cw + } +} + +type cwHijacker struct { + *compressWriter + http.Hijacker +} + +var _ http.Hijacker = cwHijacker{} + +type cwCloseNotifier struct { + *compressWriter + http.CloseNotifier +} + +var _ http.CloseNotifier = cwCloseNotifier{} + +type cwPusher struct { + *compressWriter + http.Pusher +} + +var _ http.Pusher = cwPusher{} + +type cwHijackCloseNotifier struct { + *compressWriter + iHijackCloseNotifier +} + +type iHijackCloseNotifier interface { + http.Hijacker + http.CloseNotifier +} + +var ( + _ http.Hijacker = cwHijackCloseNotifier{} + _ http.CloseNotifier = cwHijackCloseNotifier{} +) + +type cwHijackPusher struct { + *compressWriter + iHijackPusher +} + +type iHijackPusher interface { + http.Hijacker + http.Pusher +} + +var ( + _ http.Hijacker = cwHijackPusher{} + _ http.Pusher = cwHijackPusher{} +) + +type cwPushCloseNotifier struct { + *compressWriter + iPushCloseNotifier +} + +type iPushCloseNotifier interface { + http.Pusher + http.CloseNotifier +} + +var ( + _ http.Pusher = cwPushCloseNotifier{} + _ http.CloseNotifier = cwPushCloseNotifier{} +) + +type cwHijackPushCloseNotifier struct { + *compressWriter + iHijackPushCloseNotifier +} + +type iHijackPushCloseNotifier interface { + http.Hijacker + http.Pusher + http.CloseNotifier +} + +var ( + _ http.Hijacker = cwHijackPushCloseNotifier{} + _ http.Pusher = cwHijackPushCloseNotifier{} + _ http.CloseNotifier = cwHijackPushCloseNotifier{} +) diff --git a/response_writer.go b/response_writer.go index 253bc29..3356033 100644 --- a/response_writer.go +++ b/response_writer.go @@ -1,10 +1,8 @@ package httpcompression import ( - "bufio" "fmt" "io" - "net" "net/http" "strconv" "sync" @@ -31,21 +29,6 @@ type compressWriter struct { var ( _ io.WriteCloser = &compressWriter{} _ http.Flusher = &compressWriter{} - _ http.Hijacker = &compressWriter{} -) - -type compressWriterWithCloseNotify struct { - *compressWriter -} - -func (w compressWriterWithCloseNotify) CloseNotify() <-chan bool { - return w.ResponseWriter.(http.CloseNotifier).CloseNotify() -} - -var ( - _ io.WriteCloser = compressWriterWithCloseNotify{} - _ http.Flusher = compressWriterWithCloseNotify{} - _ http.Hijacker = compressWriterWithCloseNotify{} ) const maxBuf = 1 << 16 // maximum size of recycled buffer @@ -225,15 +208,6 @@ func (w *compressWriter) Flush() { } } -// Hijack implements http.Hijacker. If the underlying ResponseWriter is a -// Hijacker, its Hijack method is returned. Otherwise an error is returned. -func (w *compressWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { - if hj, ok := w.ResponseWriter.(http.Hijacker); ok { - return hj.Hijack() - } - return nil, nil, fmt.Errorf("http.Hijacker interface is not supported") -} - func (w *compressWriter) recycleBuffer() { if cap(w.buf) > 0 && cap(w.buf) <= maxBuf { w.pool.Put(w.buf[:0]) From 61d7d9d3a36e30ce354635291276f91db88cb6bb Mon Sep 17 00:00:00 2001 From: Carlo Alberto Ferraris Date: Wed, 27 Jan 2021 10:29:15 +0900 Subject: [PATCH 2/4] wip --- optional.go | 116 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 66 insertions(+), 50 deletions(-) diff --git a/optional.go b/optional.go index 5d300de..fd6043d 100644 --- a/optional.go +++ b/optional.go @@ -1,6 +1,8 @@ package httpcompression import ( + "bufio" + "net" "net/http" ) @@ -9,55 +11,70 @@ import ( // Currently the supported optional interfaces are http.Hijacker, http.Pusher, // and http.CloseNotifier. func extend(cw *compressWriter) http.ResponseWriter { - switch r := cw.ResponseWriter.(type) { - case iHijackPushCloseNotifier: - return cwHijackPushCloseNotifier{cw, r} - case iPushCloseNotifier: - return cwPushCloseNotifier{cw, r} - case iHijackPusher: - return cwHijackPusher{cw, r} - case iHijackCloseNotifier: - return cwHijackCloseNotifier{cw, r} + switch cw.ResponseWriter.(type) { + case interface { + http.Hijacker + http.Pusher + http.CloseNotifier + }: + return cwHijackPushCloseNotifier{cw} + case interface { + http.Pusher + http.CloseNotifier + }: + return cwPushCloseNotifier{cw} + case interface { + http.Hijacker + http.Pusher + }: + return cwHijackPusher{cw} + case interface { + http.Hijacker + http.CloseNotifier + }: + return cwHijackCloseNotifier{cw} case http.CloseNotifier: - return cwCloseNotifier{cw, r} + return cwCloseNotifier{cw} case http.Hijacker: - return cwHijacker{cw, r} + return cwHijacker{cw} case http.Pusher: - return cwPusher{cw, r} + return cwPusher{cw} default: return cw } } -type cwHijacker struct { - *compressWriter - http.Hijacker +type cwHijacker struct{ *compressWriter } + +func (cw cwHijacker) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return cw.ResponseWriter.(http.Hijacker).Hijack() } var _ http.Hijacker = cwHijacker{} -type cwCloseNotifier struct { - *compressWriter - http.CloseNotifier +type cwCloseNotifier struct{ *compressWriter } + +func (cw cwCloseNotifier) CloseNotify() <-chan bool { + return cw.ResponseWriter.(http.CloseNotifier).CloseNotify() } var _ http.CloseNotifier = cwCloseNotifier{} -type cwPusher struct { - *compressWriter - http.Pusher +type cwPusher struct{ *compressWriter } + +func (cw cwPusher) Push(target string, opts *http.PushOptions) error { + return cw.ResponseWriter.(http.Pusher).Push(target, opts) } var _ http.Pusher = cwPusher{} -type cwHijackCloseNotifier struct { - *compressWriter - iHijackCloseNotifier -} +type cwHijackCloseNotifier struct{ *compressWriter } -type iHijackCloseNotifier interface { - http.Hijacker - http.CloseNotifier +func (cw cwHijackCloseNotifier) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return cwHijacker{cw.compressWriter}.Hijack() +} +func (cw cwHijackCloseNotifier) CloseNotify() <-chan bool { + return cwCloseNotifier{cw.compressWriter}.CloseNotify() } var ( @@ -65,14 +82,13 @@ var ( _ http.CloseNotifier = cwHijackCloseNotifier{} ) -type cwHijackPusher struct { - *compressWriter - iHijackPusher -} +type cwHijackPusher struct{ *compressWriter } -type iHijackPusher interface { - http.Hijacker - http.Pusher +func (cw cwHijackPusher) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return cwHijacker{cw.compressWriter}.Hijack() +} +func (cw cwHijackPusher) Push(target string, opts *http.PushOptions) error { + return cwPusher{cw.compressWriter}.Push(target, opts) } var ( @@ -80,14 +96,13 @@ var ( _ http.Pusher = cwHijackPusher{} ) -type cwPushCloseNotifier struct { - *compressWriter - iPushCloseNotifier -} +type cwPushCloseNotifier struct{ *compressWriter } -type iPushCloseNotifier interface { - http.Pusher - http.CloseNotifier +func (cw cwPushCloseNotifier) Push(target string, opts *http.PushOptions) error { + return cwPusher{cw.compressWriter}.Push(target, opts) +} +func (cw cwPushCloseNotifier) CloseNotify() <-chan bool { + return cwCloseNotifier{cw.compressWriter}.CloseNotify() } var ( @@ -95,15 +110,16 @@ var ( _ http.CloseNotifier = cwPushCloseNotifier{} ) -type cwHijackPushCloseNotifier struct { - *compressWriter - iHijackPushCloseNotifier -} +type cwHijackPushCloseNotifier struct{ *compressWriter } -type iHijackPushCloseNotifier interface { - http.Hijacker - http.Pusher - http.CloseNotifier +func (cw cwHijackPushCloseNotifier) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return cwHijacker{cw.compressWriter}.Hijack() +} +func (cw cwHijackPushCloseNotifier) Push(target string, opts *http.PushOptions) error { + return cwPusher{cw.compressWriter}.Push(target, opts) +} +func (cw cwHijackPushCloseNotifier) CloseNotify() <-chan bool { + return cwCloseNotifier{cw.compressWriter}.CloseNotify() } var ( From e90a21f08d67f04c95e347ac903e7d912a169ae2 Mon Sep 17 00:00:00 2001 From: Carlo Alberto Ferraris Date: Wed, 27 Jan 2021 17:09:16 +0900 Subject: [PATCH 3/4] comment --- optional.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/optional.go b/optional.go index fd6043d..39fb94c 100644 --- a/optional.go +++ b/optional.go @@ -10,6 +10,10 @@ import ( // dynamically exposes some optional interfaces of http.ResponseWriter. // Currently the supported optional interfaces are http.Hijacker, http.Pusher, // and http.CloseNotifier. +// This is obviously an horrible way of doing things, but it's really unavoidable +// without proper language support for interface extension; see +// https://blog.merovius.de/2017/07/30/the-trouble-with-optional-interfaces.html +// for details. func extend(cw *compressWriter) http.ResponseWriter { switch cw.ResponseWriter.(type) { case interface { From a8e089c4ad5b1732658315a2347cf4093e3ac980 Mon Sep 17 00:00:00 2001 From: Carlo Alberto Ferraris Date: Fri, 29 Jan 2021 10:16:30 +0900 Subject: [PATCH 4/4] comment --- response_writer.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/response_writer.go b/response_writer.go index 3356033..3234d1d 100644 --- a/response_writer.go +++ b/response_writer.go @@ -196,8 +196,13 @@ func (w *compressWriter) Flush() { return } - // Flush the compressor, if supported, - // note: http.ResponseWriter does not implement Flusher, so we need to call ResponseWriter.Flush anyway. + // Flush the compressor, if supported. + // note: http.ResponseWriter does not implement Flusher (http.Flusher does not return an error), + // so we need to later call ResponseWriter.Flush anyway: + // - in case we are bypassing compression, w.w is the parent ResponseWriter, and therefore we skip + // this as the parent ResponseWriter does not implement Flusher. + // - in case we are NOT bypassing compression, w.w is the compressor, and therefore we flush the + // compressor and then we flush the parent ResponseWriter. if fw, ok := w.w.(Flusher); ok { _ = fw.Flush() }