From fe353c4744c6231b637bd3543680166589133472 Mon Sep 17 00:00:00 2001 From: orangedeng Date: Wed, 10 Oct 2018 14:59:39 +0800 Subject: [PATCH] Add gzip and cache controll for http response --- server/responsewriter/cache.go | 35 +++++++++++++++++++++++++++++ server/responsewriter/content.go | 31 +++++++++++++++++++++++++ server/responsewriter/gzip.go | 31 +++++++++++++++++++++++++ server/responsewriter/middleware.go | 24 ++++++++++++++++++++ server/server.go | 9 ++++---- 5 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 server/responsewriter/cache.go create mode 100644 server/responsewriter/content.go create mode 100644 server/responsewriter/gzip.go create mode 100644 server/responsewriter/middleware.go diff --git a/server/responsewriter/cache.go b/server/responsewriter/cache.go new file mode 100644 index 00000000000..dde4ff2e267 --- /dev/null +++ b/server/responsewriter/cache.go @@ -0,0 +1,35 @@ +package responsewriter + +import ( + "net/http" + "strings" + + "github.com/gorilla/mux" +) + +func CacheMiddleware(suffixes ...string) mux.MiddlewareFunc { + return mux.MiddlewareFunc(func(handler http.Handler) http.Handler { + return Cache(handler, suffixes...) + }) +} + +func Cache(handler http.Handler, suffixes ...string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + i := strings.LastIndex(r.URL.Path, ".") + if i >= 0 { + for _, suffix := range suffixes { + if suffix == r.URL.Path[i+1:] { + w.Header().Set("Cache-Control", "max-age=31536000, public") + } + } + } + handler.ServeHTTP(w, r) + }) +} + +func NoCache(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") + handler.ServeHTTP(w, r) + }) +} diff --git a/server/responsewriter/content.go b/server/responsewriter/content.go new file mode 100644 index 00000000000..16195fe52a1 --- /dev/null +++ b/server/responsewriter/content.go @@ -0,0 +1,31 @@ +package responsewriter + +import ( + "net/http" + "strings" +) + +type ContentTypeWriter struct { + http.ResponseWriter +} + +func (c ContentTypeWriter) Write(b []byte) (int, error) { + found := false + for k := range c.Header() { + if strings.EqualFold(k, "Content-Type") { + found = true + break + } + } + if !found { + c.Header().Set("Content-Type", http.DetectContentType(b)) + } + return c.ResponseWriter.Write(b) +} + +func ContentType(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + writer := ContentTypeWriter{ResponseWriter: w} + handler.ServeHTTP(writer, r) + }) +} diff --git a/server/responsewriter/gzip.go b/server/responsewriter/gzip.go new file mode 100644 index 00000000000..a43918d0aab --- /dev/null +++ b/server/responsewriter/gzip.go @@ -0,0 +1,31 @@ +package responsewriter + +import ( + "compress/gzip" + "io" + "net/http" + "strings" +) + +type gzipResponseWriter struct { + io.Writer + http.ResponseWriter +} + +func (w gzipResponseWriter) Write(b []byte) (int, error) { + return w.Writer.Write(b) +} + +func Gzip(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { + handler.ServeHTTP(w, r) + return + } + gz := gzip.NewWriter(w) + defer gz.Close() + gzw := gzipResponseWriter{Writer: gz, ResponseWriter: w} + gzw.Header().Set("Content-Encoding", "gzip") + handler.ServeHTTP(gzw, r) + }) +} diff --git a/server/responsewriter/middleware.go b/server/responsewriter/middleware.go new file mode 100644 index 00000000000..f8f2b7eef1a --- /dev/null +++ b/server/responsewriter/middleware.go @@ -0,0 +1,24 @@ +package responsewriter + +import ( + "net/http" + + "github.com/gorilla/mux" +) + +type MiddlewareChain struct { + middleWares []mux.MiddlewareFunc +} + +func NewMiddlewareChain(middleWares ...mux.MiddlewareFunc) *MiddlewareChain { + return &MiddlewareChain{middleWares: middleWares} +} + +func (m *MiddlewareChain) Handler(handler http.Handler) http.Handler { + rtn := handler + for i := len(m.middleWares) - 1; i >= 0; i-- { + w := m.middleWares[i] + rtn = w.Middleware(rtn) + } + return rtn +} diff --git a/server/server.go b/server/server.go index 8d3264d7a03..b6d33a7e25d 100644 --- a/server/server.go +++ b/server/server.go @@ -21,6 +21,7 @@ import ( "github.com/rancher/rancher/pkg/rkenodeconfigserver" "github.com/rancher/rancher/pkg/telemetry" "github.com/rancher/rancher/server/capabilities" + "github.com/rancher/rancher/server/responsewriter" "github.com/rancher/rancher/server/ui" "github.com/rancher/rancher/server/whitelist" managementSchema "github.com/rancher/types/apis/management.cattle.io/v3/schema" @@ -64,8 +65,8 @@ func Start(ctx context.Context, httpPort, httpsPort int, scaledContext *config.S connectHandler, connectConfigHandler := connectHandlers(scaledContext) samlRoot := saml.AuthHandler() - - root.Handle("/", ui.UI(managementAPI)) + chain := responsewriter.NewMiddlewareChain(responsewriter.Gzip, responsewriter.NoCache, responsewriter.ContentType, ui.UI) + root.Handle("/", chain.Handler(managementAPI)) root.PathPrefix("/v3-public").Handler(publicAPI) root.Handle("/v3/import/{token}.yaml", http.HandlerFunc(clusterregistrationtokens.ClusterImportHandler)) root.Handle("/v3/connect", connectHandler) @@ -79,11 +80,11 @@ func Start(ctx context.Context, httpPort, httpsPort int, scaledContext *config.S root.PathPrefix("/k8s/clusters/").Handler(authedHandler) root.PathPrefix("/meta").Handler(authedHandler) root.PathPrefix("/v1-telemetry").Handler(authedHandler) - root.NotFoundHandler = ui.UI(http.NotFoundHandler()) + root.NotFoundHandler = chain.Handler(http.NotFoundHandler()) root.PathPrefix("/v1-saml").Handler(samlRoot) // UI - uiContent := ui.Content() + uiContent := responsewriter.NewMiddlewareChain(responsewriter.Gzip, responsewriter.CacheMiddleware("json", "js", "css")).Handler(ui.Content()) root.PathPrefix("/assets").Handler(uiContent) root.PathPrefix("/translations").Handler(uiContent) root.Handle("/humans.txt", uiContent)