MM-25095: Handles static JS and CSS using Brotli encoding. (#14524)

* MM-25095: Handles static JS and CSS files using Brotli.

* MM-25095: Linting fix.

* MM-25095: Adds missing license.

* MM-25095: Moves initialization of slice.

* MM-25095: Moves initialization of slice.
Этот коммит содержится в:
Martin Kraft
2020-05-19 08:40:13 -04:00
коммит произвёл GitHub
родитель 6ae9513474
Коммит d7cb890f34
17 изменённых файлов: 200 добавлений и 31 удалений

Просмотреть файл

@@ -14,7 +14,7 @@ import (
"strings"
"time"
"github.com/NYTimes/gziphandler"
"github.com/mkraft/gziphandler"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
spanlog "github.com/opentracing/opentracing-go/log"

Просмотреть файл

@@ -9,16 +9,23 @@ import (
"path/filepath"
"strings"
"github.com/NYTimes/gziphandler"
"github.com/mattermost/mattermost-server/v5/mlog"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/utils"
"github.com/mattermost/mattermost-server/v5/utils/fileutils"
"github.com/mkraft/gziphandler"
)
var robotsTxt = []byte("User-agent: *\nDisallow: /\n")
// the static content types that are Brotli encoded rather than gzipped.
var brotliEncodedContent = map[string]string{
"js": "application/javascript",
"css": "text/css",
}
var brotliContentTypes []string
func (w *Web) InitStatic() {
if *w.ConfigService.Config().ServiceSettings.WebserverMode != "disabled" {
if err := utils.UpdateAssetsSubpathFromConfig(w.ConfigService.Config()); err != nil {
@@ -30,11 +37,20 @@ func (w *Web) InitStatic() {
subpath, _ := utils.GetSubpathFromConfig(w.ConfigService.Config())
staticHandler := staticFilesHandler(http.StripPrefix(path.Join(subpath, "static"), http.FileServer(http.Dir(staticDir))))
staticHandler := brotliFilesHandler(staticFilesHandler(http.StripPrefix(path.Join(subpath, "static"), http.FileServer(http.Dir(staticDir)))))
pluginHandler := staticFilesHandler(http.StripPrefix(path.Join(subpath, "static", "plugins"), http.FileServer(http.Dir(*w.ConfigService.Config().PluginSettings.ClientDirectory))))
if *w.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
staticHandler = gziphandler.GzipHandler(staticHandler)
for _, ct := range brotliEncodedContent {
brotliContentTypes = append(brotliContentTypes, ct)
}
everythingExceptBrotliGzipHandler, err := gziphandler.GzipHandlerWithOpts(gziphandler.ContentTypeExceptions(brotliContentTypes))
if err != nil {
mlog.Error("Failed to initialize gziphandler", mlog.Err(err))
}
staticHandler = everythingExceptBrotliGzipHandler(staticHandler)
pluginHandler = gziphandler.GzipHandler(pluginHandler)
}
@@ -72,6 +88,26 @@ func root(c *Context, w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, filepath.Join(staticDir, "root.html"))
}
func acceptsEncodingBrotli(r *http.Request) bool {
directives := strings.Fields(r.Header.Get("Accept-Encoding"))
for _, directive := range directives {
if strings.ToLower(directive) == "br" {
return true
}
}
return false
}
func requestingBrotliFileExtension(r *http.Request) (bool, string) {
extension := r.URL.Path[strings.LastIndex(r.URL.Path, ".")+1:]
for bx, ct := range brotliEncodedContent {
if bx == extension {
return true, ct
}
}
return false, ""
}
func staticFilesHandler(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//wrap our ResponseWriter with our no-cache 404-handler
@@ -83,6 +119,20 @@ func staticFilesHandler(handler http.Handler) http.Handler {
http.NotFound(w, r)
return
}
handler.ServeHTTP(w, r)
})
}
func brotliFilesHandler(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
isRequestingBrotliFile, contentType := requestingBrotliFileExtension(r)
if isRequestingBrotliFile && acceptsEncodingBrotli(r) {
r.URL.Path = r.URL.Path + ".br"
w.Header().Set("Content-Encoding", "br")
w.Header().Set("Content-Type", contentType)
}
handler.ServeHTTP(w, r)
})
}

58
web/static_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,58 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package web
import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
var tests = []struct {
requestURL string
requestContentType string
requestAcceptEncoding []string
expectBrotli bool
}{
{"http://test.com/foo.js", "application/javascript", []string{"br"}, true},
{"http://test.com/foo.css", "text/css", []string{"br"}, true},
{"http://test.com/foo.jss", "text/plain; charset=utf-8", []string{"gzip"}, false},
{"http://test.com/foo.css", "text/plain; charset=utf-8", []string{"gzip"}, false},
{"http://test.com/foo.jsx", "text/plain; charset=utf-8", []string{"br"}, false},
{"http://test.com/foo.xcss", "text/plain; charset=utf-8", []string{"gzip"}, false},
}
type mockHandler struct{}
func (mh mockHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello")
}
func TestBrotliFilesHandler(t *testing.T) {
for _, tt := range tests {
t.Run(fmt.Sprintf("%v", tt), func(t *testing.T) {
req := httptest.NewRequest("GET", tt.requestURL, nil)
req.Header.Set("Accept-Encoding", strings.Join(tt.requestAcceptEncoding, ", "))
w := httptest.NewRecorder()
handler := brotliFilesHandler(mockHandler{})
handler.ServeHTTP(w, req)
resp := w.Result()
require.Equal(t, tt.expectBrotli, resp.Header.Get("Content-Encoding") == "br")
if tt.expectBrotli {
require.Equal(t, tt.requestURL+".br", req.URL.String())
} else {
require.Equal(t, tt.requestURL, req.URL.String())
}
require.Equal(t, tt.requestContentType, resp.Header.Get("Content-Type"))
})
}
}