github.com/gocaveman/caveman@v0.0.0-20191211162744-0ddf99dbdf6e/webutil/htmlmin/htmlmin.go (about) 1 // Provides a minifier Handler for text/html output. 2 package htmlmin 3 4 import ( 5 "bufio" 6 "io" 7 "mime" 8 "net" 9 "net/http" 10 "regexp" 11 12 "github.com/tdewolff/minify" 13 "github.com/tdewolff/minify/css" 14 "github.com/tdewolff/minify/html" 15 "github.com/tdewolff/minify/js" 16 "github.com/tdewolff/minify/json" 17 "github.com/tdewolff/minify/svg" 18 "github.com/tdewolff/minify/xml" 19 ) 20 21 // NewHandler returns a new HTML minifying handler with the default settings. For simple use all that is needed is to 22 // add this early on in your handler list. 23 func NewHandler() *HTMLMinHandler { 24 // we register all this other stuff because the HTML minifier might need it for other content types inline. 25 m := minify.New() 26 m.AddFunc("text/css", css.Minify) 27 m.AddFunc("text/javascript", js.Minify) 28 m.AddFunc("image/svg+xml", svg.Minify) 29 m.AddFuncRegexp(regexp.MustCompile("[/+]json$"), json.Minify) 30 m.AddFuncRegexp(regexp.MustCompile("[/+]xml$"), xml.Minify) 31 32 // some of these defaults are a bit much, tune it back a bit 33 m.Add("text/html", &html.Minifier{ 34 KeepConditionalComments: true, 35 KeepDefaultAttrVals: true, 36 KeepDocumentTags: true, 37 KeepEndTags: true, 38 }) 39 40 return &HTMLMinHandler{ 41 M: m, 42 } 43 } 44 45 // HTMLMinHandler replaces the ResponseWriter with one that minifies HTML as it's written. 46 type HTMLMinHandler struct { 47 M *minify.M // public in case you want to replace the minifier to change it's behavior 48 } 49 50 func (h *HTMLMinHandler) ServeHTTPChain(w http.ResponseWriter, r *http.Request) (http.ResponseWriter, *http.Request) { 51 52 retw := &HTMLMinResponseWriter{ 53 ResponseWriter: w, 54 minw: nil, // starts as nil, gets set on first Write() if content type is text/html 55 m: h.M, 56 } 57 return retw, r 58 59 } 60 61 // HTMLMinResponseWriter implements HTML minification on content type text/html written to it. 62 // Normally you want to use NewHandler to create an HTMLMinHandler which creates a HTMLMinResponseWriter 63 // for each request. 64 type HTMLMinResponseWriter struct { 65 http.ResponseWriter 66 minw io.WriteCloser 67 m *minify.M 68 } 69 70 // writeThrough is used in place of the minifer when minifying not enabled (wrong content type) 71 type writeThrough struct { 72 io.Writer 73 } 74 75 func (wt *writeThrough) Close() error { return nil } 76 77 func (w *HTMLMinResponseWriter) Write(p []byte) (int, error) { 78 79 if w.minw == nil { 80 81 // see if content type is set 82 ct, _, _ := mime.ParseMediaType(w.Header().Get("content-type")) 83 84 // if not set, detect 85 if ct == "" { 86 ct, _, _ = mime.ParseMediaType(http.DetectContentType(p)) 87 } 88 89 // if text/html, create writer 90 if ct == "text/html" { 91 w.minw = w.m.Writer("text/html", w.ResponseWriter) 92 93 // Send an empty write through to the underlying writer - to be sure the context cancelation works. 94 // w.minw.Write() below is not guaranteed to reach the underlying response writer in this call. 95 w.ResponseWriter.Write(nil) 96 97 } else { 98 w.minw = &writeThrough{Writer: w.ResponseWriter} 99 } 100 101 } else { 102 w.minw = &writeThrough{Writer: w.ResponseWriter} 103 } 104 105 return w.minw.Write(p) 106 } 107 108 func (w *HTMLMinResponseWriter) WriteHeader(c int) { 109 w.ResponseWriter.WriteHeader(c) 110 } 111 112 func (w *HTMLMinResponseWriter) Close() (err error) { 113 if w.minw != nil { 114 w.minw.Close() 115 w.minw = nil 116 } 117 if c, ok := w.ResponseWriter.(io.Closer); ok { 118 err = c.Close() 119 } 120 return err 121 } 122 123 func (w *HTMLMinResponseWriter) Flush() { 124 if w.minw != nil { 125 w.minw.Close() 126 w.minw = nil 127 } 128 w.ResponseWriter.(http.Flusher).Flush() 129 } 130 131 func (w *HTMLMinResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { 132 if w.minw != nil { 133 w.minw.Close() 134 w.minw = nil 135 } 136 return w.ResponseWriter.(http.Hijacker).Hijack() 137 } 138 139 func (w *HTMLMinResponseWriter) CloseNotify() <-chan bool { 140 return w.ResponseWriter.(http.CloseNotifier).CloseNotify() 141 } 142 143 func (w *HTMLMinResponseWriter) Push(target string, opts *http.PushOptions) error { 144 return w.ResponseWriter.(http.Pusher).Push(target, opts) 145 }