Permalink
Find file Copy path
149 lines (123 sloc) 3.4 KB
// Copyright 2013 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package handlers
import (
"compress/flate"
"compress/gzip"
"io"
"net/http"
"strings"
)
type compressResponseWriter struct {
io.Writer
http.ResponseWriter
http.Hijacker
http.Flusher
http.CloseNotifier
}
func (w *compressResponseWriter) WriteHeader(c int) {
w.ResponseWriter.Header().Del("Content-Length")
w.ResponseWriter.WriteHeader(c)
}
func (w *compressResponseWriter) Header() http.Header {
return w.ResponseWriter.Header()
}
func (w *compressResponseWriter) Write(b []byte) (int, error) {
h := w.ResponseWriter.Header()
if h.Get("Content-Type") == "" {
h.Set("Content-Type", http.DetectContentType(b))
}
h.Del("Content-Length")
return w.Writer.Write(b)
}
type flusher interface {
Flush() error
}
func (w *compressResponseWriter) Flush() {
// Flush compressed data if compressor supports it.
if f, ok := w.Writer.(flusher); ok {
f.Flush()
}
// Flush HTTP response.
if w.Flusher != nil {
w.Flusher.Flush()
}
}
// CompressHandler gzip compresses HTTP responses for clients that support it
// via the 'Accept-Encoding' header.
//
// Compressing TLS traffic may leak the page contents to an attacker if the
// page contains user input: http://security.stackexchange.com/a/102015/12208
func CompressHandler(h http.Handler) http.Handler {
return CompressHandlerLevel(h, gzip.DefaultCompression)
}
// CompressHandlerLevel gzip compresses HTTP responses with specified compression level
// for clients that support it via the 'Accept-Encoding' header.
//
// The compression level should be gzip.DefaultCompression, gzip.NoCompression,
// or any integer value between gzip.BestSpeed and gzip.BestCompression inclusive.
// gzip.DefaultCompression is used in case of invalid compression level.
func CompressHandlerLevel(h http.Handler, level int) http.Handler {
if level < gzip.DefaultCompression || level > gzip.BestCompression {
level = gzip.DefaultCompression
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
L:
for _, enc := range strings.Split(r.Header.Get("Accept-Encoding"), ",") {
switch strings.TrimSpace(enc) {
case "gzip":
w.Header().Set("Content-Encoding", "gzip")
w.Header().Add("Vary", "Accept-Encoding")
gw, _ := gzip.NewWriterLevel(w, level)
defer gw.Close()
h, hok := w.(http.Hijacker)
if !hok { /* w is not Hijacker... oh well... */
h = nil
}
f, fok := w.(http.Flusher)
if !fok {
f = nil
}
cn, cnok := w.(http.CloseNotifier)
if !cnok {
cn = nil
}
w = &compressResponseWriter{
Writer: gw,
ResponseWriter: w,
Hijacker: h,
Flusher: f,
CloseNotifier: cn,
}
break L
case "deflate":
w.Header().Set("Content-Encoding", "deflate")
w.Header().Add("Vary", "Accept-Encoding")
fw, _ := flate.NewWriter(w, level)
defer fw.Close()
h, hok := w.(http.Hijacker)
if !hok { /* w is not Hijacker... oh well... */
h = nil
}
f, fok := w.(http.Flusher)
if !fok {
f = nil
}
cn, cnok := w.(http.CloseNotifier)
if !cnok {
cn = nil
}
w = &compressResponseWriter{
Writer: fw,
ResponseWriter: w,
Hijacker: h,
Flusher: f,
CloseNotifier: cn,
}
break L
}
}
h.ServeHTTP(w, r)
})
}