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  }