@@ -1,19 +1,22 @@
package main
import (
"compress/gzip"
"context"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"time"
"github.com/shurcooL/httperror"
"github.com/shurcooL/users"
"golang.org/x/net/http/httpguts"
)
// errorHandler factors error handling out of the HTTP handler.
type errorHandler struct {
handler func(w http.ResponseWriter, req *http.Request) error
@@ -105,10 +108,80 @@ func (rw *headerResponseWriter) Write(p []byte) (n int, err error) {
func (rw *headerResponseWriter) WriteHeader(code int) {
rw.WroteHeader = true
rw.ResponseWriter.WriteHeader(code)
}
// gzipHandler applies gzip compression on top of Handler, unless Handler
// has already handled it (i.e., the "Content-Encoding" header is set).
type gzipHandler struct {
Handler http.Handler
}
func (h gzipHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// If request doesn't accept gzip encoding, serve without compression.
if !httpguts.HeaderValuesContainsToken(req.Header["Accept-Encoding"], "gzip") {
h.Handler.ServeHTTP(w, req)
return
}
// Otherwise, use gzipResponseWriter to start gzip compression when WriteHeader
// is called, but only if the handler didn't already take care of it.
rw := &gzipResponseWriter{ResponseWriter: w}
defer rw.Close()
h.Handler.ServeHTTP(rw, req)
}
// gzipResponseWriter starts gzip compression when WriteHeader is called, unless compression
// has already been applied by that time (i.e., the "Content-Encoding" header is set).
type gzipResponseWriter struct {
http.ResponseWriter
// These fields are set by setWriterAndCloser
// during first call to Write or WriteHeader.
w io.Writer // When set, must be non-nil.
c io.Closer // May be nil.
}
func (rw *gzipResponseWriter) WriteHeader(code int) {
if rw.w != nil {
panic(fmt.Errorf("internal error: gzipResponseWriter: WriteHeader called twice or after Write"))
}
rw.setWriterAndCloser()
rw.ResponseWriter.WriteHeader(code)
}
func (rw *gzipResponseWriter) Write(p []byte) (n int, err error) {
if rw.w == nil {
rw.setWriterAndCloser()
}
return rw.w.Write(p)
}
func (rw *gzipResponseWriter) setWriterAndCloser() {
if _, ok := rw.Header()["Content-Encoding"]; ok {
// Compression already handled by the handler.
rw.w = rw.ResponseWriter
return
}
// Update headers, start using a gzip writer.
rw.Header().Set("Content-Encoding", "gzip")
rw.Header().Del("Content-Length")
gw := gzip.NewWriter(rw.ResponseWriter)
rw.w = gw
rw.c = gw
}
func (rw *gzipResponseWriter) Close() {
if rw.c == nil {
return
}
err := rw.c.Close()
if err != nil {
log.Printf("gzipResponseWriter.Close: error closing *gzip.Writer: %v", err)
}
}
// top adds some instrumentation on top of Handler.
type top struct{ Handler http.Handler }
func (t top) ServeHTTP(w http.ResponseWriter, req *http.Request) {
path := req.URL.Path