From 0b38ed2227cd13196056188f0854373ed6ad2db6 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 3 Jun 2019 13:12:06 +0800 Subject: [PATCH 1/6] Don't unzip files from bindata but send to browser directly --- go.mod | 1 + go.sum | 2 + modules/public/public.go | 29 +++ .../github.com/shurcooL/httpgzip/.travis.yml | 16 ++ vendor/github.com/shurcooL/httpgzip/LICENSE | 21 ++ vendor/github.com/shurcooL/httpgzip/README.md | 19 ++ vendor/github.com/shurcooL/httpgzip/doc.go | 3 + vendor/github.com/shurcooL/httpgzip/fs.go | 226 ++++++++++++++++++ vendor/github.com/shurcooL/httpgzip/gzip.go | 115 +++++++++ vendor/modules.txt | 3 + 10 files changed, 435 insertions(+) create mode 100644 vendor/github.com/shurcooL/httpgzip/.travis.yml create mode 100644 vendor/github.com/shurcooL/httpgzip/LICENSE create mode 100644 vendor/github.com/shurcooL/httpgzip/README.md create mode 100644 vendor/github.com/shurcooL/httpgzip/doc.go create mode 100644 vendor/github.com/shurcooL/httpgzip/fs.go create mode 100644 vendor/github.com/shurcooL/httpgzip/gzip.go diff --git a/go.mod b/go.mod index c334f68831a6a..b7c0ee1ff343c 100644 --- a/go.mod +++ b/go.mod @@ -87,6 +87,7 @@ require ( github.com/quasoft/websspi v1.0.0 github.com/sergi/go-diff v1.1.0 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect + github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0 github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 github.com/spf13/viper v1.7.1 // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect diff --git a/go.sum b/go.sum index 6ac80e2677c97..13d69f367ff87 100644 --- a/go.sum +++ b/go.sum @@ -986,6 +986,8 @@ github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t4 github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0 h1:mj/nMDAwTBiaCqMEs4cYCqF7pO6Np7vhy1D1wcQGz+E= +github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU= diff --git a/modules/public/public.go b/modules/public/public.go index fc933637d8f6e..df576d6e2584e 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -5,7 +5,11 @@ package public import ( + "bytes" + "compress/gzip" + "io" "log" + "mime" "net/http" "path" "path/filepath" @@ -13,6 +17,8 @@ import ( "code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/setting" + + "github.com/shurcooL/httpgzip" ) // Options represents the available options to configure the macaron handler. @@ -157,6 +163,29 @@ func (opts *Options) handle(w http.ResponseWriter, req *http.Request, opt *Optio return true } + if _, ok := f.(httpgzip.NotWorthGzipCompressing); !ok { + if g, ok := f.(httpgzip.GzipByter); ok { + w.Header().Set("Content-Encoding", "gzip") + rd := bytes.NewReader(g.GzipBytes()) + ctype := mime.TypeByExtension(filepath.Ext(fi.Name())) + if ctype == "" { + // read a chunk to decide between utf-8 text and binary + var buf [512]byte + grd, _ := gzip.NewReader(rd) + n, _ := io.ReadFull(grd, buf[:]) + ctype = http.DetectContentType(buf[:n]) + _, err := rd.Seek(0, io.SeekStart) // rewind to output whole file + if err != nil { + log.Printf("rd.Seek error: %v\n", err) + return false + } + } + w.Header().Set("Content-Type", ctype) + http.ServeContent(w, req, file, fi.ModTime(), rd) + return true + } + } + http.ServeContent(w, req, file, fi.ModTime(), f) return true } diff --git a/vendor/github.com/shurcooL/httpgzip/.travis.yml b/vendor/github.com/shurcooL/httpgzip/.travis.yml new file mode 100644 index 0000000000000..437c57db3d00d --- /dev/null +++ b/vendor/github.com/shurcooL/httpgzip/.travis.yml @@ -0,0 +1,16 @@ +sudo: false +language: go +go: + - 1.x + - master +matrix: + allow_failures: + - go: master + fast_finish: true +install: + - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step). +script: + - go get -t -v ./... + - diff -u <(echo -n) <(gofmt -d -s .) + - go vet ./... + - go test -v -race ./... diff --git a/vendor/github.com/shurcooL/httpgzip/LICENSE b/vendor/github.com/shurcooL/httpgzip/LICENSE new file mode 100644 index 0000000000000..27a767b1f53d2 --- /dev/null +++ b/vendor/github.com/shurcooL/httpgzip/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Dmitri Shuralyov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/shurcooL/httpgzip/README.md b/vendor/github.com/shurcooL/httpgzip/README.md new file mode 100644 index 0000000000000..a38c2565e2859 --- /dev/null +++ b/vendor/github.com/shurcooL/httpgzip/README.md @@ -0,0 +1,19 @@ +httpgzip +======== + +[![Build Status](https://travis-ci.org/shurcooL/httpgzip.svg?branch=master)](https://travis-ci.org/shurcooL/httpgzip) [![GoDoc](https://godoc.org/github.com/shurcooL/httpgzip?status.svg)](https://godoc.org/github.com/shurcooL/httpgzip) + +Package httpgzip provides net/http-like primitives +that use gzip compression when serving HTTP requests. + +Installation +------------ + +```bash +go get -u github.com/shurcooL/httpgzip +``` + +License +------- + +- [MIT License](LICENSE) diff --git a/vendor/github.com/shurcooL/httpgzip/doc.go b/vendor/github.com/shurcooL/httpgzip/doc.go new file mode 100644 index 0000000000000..ee3bb44f30686 --- /dev/null +++ b/vendor/github.com/shurcooL/httpgzip/doc.go @@ -0,0 +1,3 @@ +// Package httpgzip provides net/http-like primitives +// that use gzip compression when serving HTTP requests. +package httpgzip diff --git a/vendor/github.com/shurcooL/httpgzip/fs.go b/vendor/github.com/shurcooL/httpgzip/fs.go new file mode 100644 index 0000000000000..33f16a309af21 --- /dev/null +++ b/vendor/github.com/shurcooL/httpgzip/fs.go @@ -0,0 +1,226 @@ +package httpgzip + +import ( + "fmt" + "html" + "net/http" + "net/url" + "os" + pathpkg "path" + "sort" + "strings" + "time" +) + +// FileServer returns a handler that serves HTTP requests +// with the contents of the file system rooted at root. +// Additional optional behaviors can be controlled via opt. +func FileServer(root http.FileSystem, opt FileServerOptions) http.Handler { + if opt.ServeError == nil { + opt.ServeError = defaults.ServeError + } + return &fileServer{root: root, opt: opt} +} + +var defaults = FileServerOptions{ + ServeError: NonSpecific, +} + +// FileServerOptions specifies options for FileServer. +type FileServerOptions struct { + // IndexHTML controls special handling of "index.html" file. + IndexHTML bool + + // ServeError is used to serve errors coming from underlying file system. + // If called, it's guaranteed to be before anything has been written + // to w by FileServer, so it's safe to use http.Error. + // If nil, then NonSpecific is used. + ServeError func(w http.ResponseWriter, req *http.Request, err error) +} + +var ( + // NonSpecific serves a non-specific HTTP error message and status code + // for a given non-nil error value. It's important that NonSpecific does not + // actually include err.Error(), since its goal is to not leak information + // in error messages to users. + NonSpecific = func(w http.ResponseWriter, req *http.Request, err error) { + switch { + case os.IsNotExist(err): + http.Error(w, "404 Not Found", http.StatusNotFound) + case os.IsPermission(err): + http.Error(w, "403 Forbidden", http.StatusForbidden) + default: + http.Error(w, "500 Internal Server Error", http.StatusInternalServerError) + } + } + + // Detailed serves detailed HTTP error message and status code for a given + // non-nil error value. Because err.Error() is displayed to users, it should + // be used in development only, or if you're confident there won't be sensitive + // information in the underlying file system error messages. + Detailed = func(w http.ResponseWriter, req *http.Request, err error) { + switch { + case os.IsNotExist(err): + http.Error(w, "404 Not Found\n\n"+err.Error(), http.StatusNotFound) + case os.IsPermission(err): + http.Error(w, "403 Forbidden\n\n"+err.Error(), http.StatusForbidden) + default: + http.Error(w, "500 Internal Server Error\n\n"+err.Error(), http.StatusInternalServerError) + } + } +) + +type fileServer struct { + root http.FileSystem + opt FileServerOptions +} + +func (fs *fileServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if req.Method != "GET" { + w.Header().Set("Allow", "GET") + http.Error(w, "405 Method Not Allowed\n\nmethod should be GET", http.StatusMethodNotAllowed) + return + } + + // Already cleaned by net/http.cleanPath, but caller can be some middleware. + path := pathpkg.Clean("/" + req.URL.Path) + + if fs.opt.IndexHTML { + // Redirect .../index.html to .../. + // Can't use Redirect() because that would make the path absolute, + // which would be a problem running under StripPrefix. + if strings.HasSuffix(path, "/index.html") { + localRedirect(w, req, ".") + return + } + } + + f, err := fs.root.Open(path) + if err != nil { + fs.opt.ServeError(w, req, err) + return + } + defer f.Close() + fi, err := f.Stat() + if err != nil { + fs.opt.ServeError(w, req, err) + return + } + + // Redirect to canonical path: / at end of directory url. + url := req.URL.Path + if fi.IsDir() { + if !strings.HasSuffix(url, "/") && url != "" { + localRedirect(w, req, pathpkg.Base(url)+"/") + return + } + } else { + if strings.HasSuffix(url, "/") && url != "/" { + localRedirect(w, req, "../"+pathpkg.Base(url)) + return + } + } + + if fs.opt.IndexHTML { + // Use contents of index.html for directory, if present. + if fi.IsDir() { + indexPath := pathpkg.Join(path, "index.html") + f0, err := fs.root.Open(indexPath) + if err == nil { + defer f0.Close() + fi0, err := f0.Stat() + if err == nil { + path = indexPath + f = f0 + fi = fi0 + } + } + } + } + + // A directory? + if fi.IsDir() { + if checkLastModified(w, req, fi.ModTime()) { + return + } + err := dirList(w, f, path == "/") + if err != nil { + fs.opt.ServeError(w, req, err) + } + return + } + + ServeContent(w, req, fi.Name(), fi.ModTime(), f) +} + +func dirList(w http.ResponseWriter, f http.File, root bool) error { + dirs, err := f.Readdir(0) + if err != nil { + return fmt.Errorf("error reading directory: %v", err) + } + sort.Sort(byName(dirs)) + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + fmt.Fprintln(w, "
")
+	switch root {
+	case true:
+		fmt.Fprintln(w, `.`)
+	case false:
+		fmt.Fprintln(w, `..`)
+	}
+	for _, d := range dirs {
+		name := d.Name()
+		if d.IsDir() {
+			name += "/"
+		}
+		// name may contain '?' or '#', which must be escaped to remain
+		// part of the URL path, and not indicate the start of a query
+		// string or fragment.
+		url := url.URL{Path: name}
+		fmt.Fprintf(w, "%s\n", url.String(), html.EscapeString(name))
+	}
+	fmt.Fprintln(w, "
") + return nil +} + +// localRedirect gives a Moved Permanently response. +// It does not convert relative paths to absolute paths like http.Redirect does. +func localRedirect(w http.ResponseWriter, req *http.Request, newPath string) { + if req.URL.RawQuery != "" { + newPath += "?" + req.URL.RawQuery + } + w.Header().Set("Location", newPath) + w.WriteHeader(http.StatusMovedPermanently) +} + +var unixEpochTime = time.Unix(0, 0) + +// modTime is the modification time of the resource to be served, or IsZero(). +// return value is whether this request is now complete. +func checkLastModified(w http.ResponseWriter, req *http.Request, modTime time.Time) bool { + if modTime.IsZero() || modTime.Equal(unixEpochTime) { + // If the file doesn't have a modTime (IsZero), or the modTime + // is obviously garbage (Unix time == 0), then ignore modtimes + // and don't process the If-Modified-Since header. + return false + } + + // The Date-Modified header truncates sub-second precision, so + // use mtime < t+1s instead of mtime <= t to check for unmodified. + if t, err := time.Parse(http.TimeFormat, req.Header.Get("If-Modified-Since")); err == nil && modTime.Before(t.Add(1*time.Second)) { + h := w.Header() + delete(h, "Content-Type") + delete(h, "Content-Length") + w.WriteHeader(http.StatusNotModified) + return true + } + w.Header().Set("Last-Modified", modTime.UTC().Format(http.TimeFormat)) + return false +} + +// byName implements sort.Interface. +type byName []os.FileInfo + +func (s byName) Len() int { return len(s) } +func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() } +func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } diff --git a/vendor/github.com/shurcooL/httpgzip/gzip.go b/vendor/github.com/shurcooL/httpgzip/gzip.go new file mode 100644 index 0000000000000..cd8ab9473d96e --- /dev/null +++ b/vendor/github.com/shurcooL/httpgzip/gzip.go @@ -0,0 +1,115 @@ +package httpgzip + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "mime" + "net/http" + "path/filepath" + "time" + + "golang.org/x/net/http/httpguts" +) + +// GzipByter is implemented by compressed files for +// efficient direct access to the internal compressed bytes. +type GzipByter interface { + // GzipBytes returns gzip compressed contents of the file. + GzipBytes() []byte +} + +// NotWorthGzipCompressing is implemented by files that were determined +// not to be worth gzip compressing (the file size did not decrease as a result). +type NotWorthGzipCompressing interface { + // NotWorthGzipCompressing is a noop. It's implemented in order to indicate + // the file is not worth gzip compressing. + NotWorthGzipCompressing() +} + +// ServeContent is like http.ServeContent, except it applies gzip compression +// if compression hasn't already been done (i.e., the "Content-Encoding" header is set). +// It's aware of GzipByter and NotWorthGzipCompressing interfaces, and uses them +// to improve performance when the provided content implements them. Otherwise, +// it applies gzip compression on the fly, if it's found to be beneficial. +func ServeContent(w http.ResponseWriter, req *http.Request, name string, modTime time.Time, content io.ReadSeeker) { + // If compression has already been dealt with, serve as is. + if _, ok := w.Header()["Content-Encoding"]; ok { + http.ServeContent(w, req, name, modTime, content) + return + } + + // If request doesn't accept gzip encoding, serve without compression. + if !httpguts.HeaderValuesContainsToken(req.Header["Accept-Encoding"], "gzip") { + http.ServeContent(w, req, name, modTime, content) + return + } + + // If the file is not worth gzip compressing, serve it as is. + if _, ok := content.(NotWorthGzipCompressing); ok { + w.Header()["Content-Encoding"] = nil + http.ServeContent(w, req, name, modTime, content) + return + } + + // The following cases involve compression, so we want to detect the Content-Type eagerly, + // before passing it off to http.ServeContent. It's because http.ServeContent won't be able + // to easily detect the original content type after content has been gzip compressed. + // We do this even for the last case that serves uncompressed data so that it doesn't + // have to do duplicate work. + _, haveType := w.Header()["Content-Type"] + if !haveType { + ctype := mime.TypeByExtension(filepath.Ext(name)) + if ctype == "" { + // Read a chunk to decide between utf-8 text and binary. + var buf [512]byte + n, _ := io.ReadFull(content, buf[:]) + ctype = http.DetectContentType(buf[:n]) + _, err := content.Seek(0, io.SeekStart) // Rewind to output whole file. + if err != nil { + http.Error(w, "500 Internal Server Error\n\nseeker can't seek", http.StatusInternalServerError) + return + } + } + w.Header().Set("Content-Type", ctype) + } + + // If there are gzip encoded bytes available, use them directly. + if gzipFile, ok := content.(GzipByter); ok { + w.Header().Set("Content-Encoding", "gzip") + http.ServeContent(w, req, name, modTime, bytes.NewReader(gzipFile.GzipBytes())) + return + } + + // Perform compression and serve gzip compressed bytes (if it's worth it). + if rs, err := gzipCompress(content); err == nil { + w.Header().Set("Content-Encoding", "gzip") + http.ServeContent(w, req, name, modTime, rs) + return + } + + // Serve as is. + w.Header()["Content-Encoding"] = nil + http.ServeContent(w, req, name, modTime, content) +} + +// gzipCompress compresses input from r and returns it as an io.ReadSeeker. +// It returns an error if compressed size is not smaller than uncompressed. +func gzipCompress(r io.Reader) (io.ReadSeeker, error) { + var buf bytes.Buffer + gw := gzip.NewWriter(&buf) + n, err := io.Copy(gw, r) + if err != nil { + // No need to gw.Close() here since we're discarding the result, and gzip.Writer.Close isn't needed for cleanup. + return nil, err + } + err = gw.Close() + if err != nil { + return nil, err + } + if int64(buf.Len()) >= n { + return nil, fmt.Errorf("not worth gzip compressing: original size %v, compressed size %v", n, buf.Len()) + } + return bytes.NewReader(buf.Bytes()), nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index a30151bf342be..55346df76086a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -683,6 +683,9 @@ github.com/sergi/go-diff/diffmatchpatch # github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 ## explicit github.com/shurcooL/httpfs/vfsutil +# github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0 +## explicit +github.com/shurcooL/httpgzip # github.com/shurcooL/sanitized_anchor_name v1.0.0 github.com/shurcooL/sanitized_anchor_name # github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 From 4ace6f9bbad1fde75dc5d76a685e3f5337c1e776 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 23 Dec 2020 21:39:49 +0800 Subject: [PATCH 2/6] remove dependent for httpgzip --- go.mod | 1 - go.sum | 2 - modules/public/dynamic.go | 11 +- modules/public/public.go | 41 +--- modules/public/static.go | 35 +++ .../github.com/shurcooL/httpgzip/.travis.yml | 16 -- vendor/github.com/shurcooL/httpgzip/LICENSE | 21 -- vendor/github.com/shurcooL/httpgzip/README.md | 19 -- vendor/github.com/shurcooL/httpgzip/doc.go | 3 - vendor/github.com/shurcooL/httpgzip/fs.go | 226 ------------------ vendor/github.com/shurcooL/httpgzip/gzip.go | 115 --------- vendor/modules.txt | 3 - 12 files changed, 56 insertions(+), 437 deletions(-) delete mode 100644 vendor/github.com/shurcooL/httpgzip/.travis.yml delete mode 100644 vendor/github.com/shurcooL/httpgzip/LICENSE delete mode 100644 vendor/github.com/shurcooL/httpgzip/README.md delete mode 100644 vendor/github.com/shurcooL/httpgzip/doc.go delete mode 100644 vendor/github.com/shurcooL/httpgzip/fs.go delete mode 100644 vendor/github.com/shurcooL/httpgzip/gzip.go diff --git a/go.mod b/go.mod index b7c0ee1ff343c..c334f68831a6a 100644 --- a/go.mod +++ b/go.mod @@ -87,7 +87,6 @@ require ( github.com/quasoft/websspi v1.0.0 github.com/sergi/go-diff v1.1.0 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect - github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0 github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 github.com/spf13/viper v1.7.1 // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect diff --git a/go.sum b/go.sum index 13d69f367ff87..6ac80e2677c97 100644 --- a/go.sum +++ b/go.sum @@ -986,8 +986,6 @@ github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t4 github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0 h1:mj/nMDAwTBiaCqMEs4cYCqF7pO6Np7vhy1D1wcQGz+E= -github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU= diff --git a/modules/public/dynamic.go b/modules/public/dynamic.go index f1a4dbb1a365e..47c0c589e306b 100644 --- a/modules/public/dynamic.go +++ b/modules/public/dynamic.go @@ -6,9 +6,18 @@ package public -import "net/http" +import ( + "io" + "net/http" + "time" +) // Static implements the macaron static handler for serving assets. func Static(opts *Options) func(next http.Handler) http.Handler { return opts.staticHandler(opts.Directory) } + +// ServeContent serve http content +func ServeContent(w http.ResponseWriter, req *http.Request, name string, modtime time.Time, content io.ReadSeeker) { + http.ServeContent(w, req, name, modtime, content) +} diff --git a/modules/public/public.go b/modules/public/public.go index df576d6e2584e..37eceef345e41 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -5,11 +5,7 @@ package public import ( - "bytes" - "compress/gzip" - "io" "log" - "mime" "net/http" "path" "path/filepath" @@ -17,8 +13,6 @@ import ( "code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/setting" - - "github.com/shurcooL/httpgzip" ) // Options represents the available options to configure the macaron handler. @@ -93,6 +87,16 @@ func (opts *Options) staticHandler(dir string) func(next http.Handler) http.Hand } } +// parseAcceptEncoding parse Accept-Encoding: deflate, gzip;q=1.0, *;q=0.5 as compress methods +func parseAcceptEncoding(val string) map[string]bool { + parts := strings.Split(val, ";") + var types = make(map[string]bool) + for _, v := range strings.Split(parts[0], ",") { + types[v] = true + } + return types +} + func (opts *Options) handle(w http.ResponseWriter, req *http.Request, opt *Options) bool { if req.Method != "GET" && req.Method != "HEAD" { return false @@ -163,29 +167,6 @@ func (opts *Options) handle(w http.ResponseWriter, req *http.Request, opt *Optio return true } - if _, ok := f.(httpgzip.NotWorthGzipCompressing); !ok { - if g, ok := f.(httpgzip.GzipByter); ok { - w.Header().Set("Content-Encoding", "gzip") - rd := bytes.NewReader(g.GzipBytes()) - ctype := mime.TypeByExtension(filepath.Ext(fi.Name())) - if ctype == "" { - // read a chunk to decide between utf-8 text and binary - var buf [512]byte - grd, _ := gzip.NewReader(rd) - n, _ := io.ReadFull(grd, buf[:]) - ctype = http.DetectContentType(buf[:n]) - _, err := rd.Seek(0, io.SeekStart) // rewind to output whole file - if err != nil { - log.Printf("rd.Seek error: %v\n", err) - return false - } - } - w.Header().Set("Content-Type", ctype) - http.ServeContent(w, req, file, fi.ModTime(), rd) - return true - } - } - - http.ServeContent(w, req, file, fi.ModTime(), f) + ServeContent(w, req, file, fi.ModTime(), f) return true } diff --git a/modules/public/static.go b/modules/public/static.go index 8da10567ead78..5a92560a5a78a 100644 --- a/modules/public/static.go +++ b/modules/public/static.go @@ -7,8 +7,14 @@ package public import ( + "compress/gzip" + "io" "io/ioutil" + "log" + "mime" "net/http" + "path/filepath" + "time" ) // Static implements the macaron static handler for serving assets. @@ -49,3 +55,32 @@ func AssetIsDir(name string) (bool, error) { } } } + +// ServeContent serve http content +func ServeContent(w http.ResponseWriter, req *http.Request, name string, modtime time.Time, content io.ReadSeeker) { + encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding")) + if encodings["gzip"] { + if rd, ok := f.(*vfsgen۰CompressedFile); ok { + w.Header().Set("Content-Encoding", "gzip") + ctype := mime.TypeByExtension(filepath.Ext(fi.Name())) + if ctype == "" { + // read a chunk to decide between utf-8 text and binary + var buf [512]byte + grd, _ := gzip.NewReader(rd) + n, _ := io.ReadFull(grd, buf[:]) + ctype = http.DetectContentType(buf[:n]) + _, err := rd.Seek(0, io.SeekStart) // rewind to output whole file + if err != nil { + log.Printf("rd.Seek error: %v\n", err) + return false + } + } + w.Header().Set("Content-Type", ctype) + http.ServeContent(w, req, file, fi.ModTime(), rd) + return true + } + } + + http.ServeContent(w, req, file, fi.ModTime(), f) + return true +} diff --git a/vendor/github.com/shurcooL/httpgzip/.travis.yml b/vendor/github.com/shurcooL/httpgzip/.travis.yml deleted file mode 100644 index 437c57db3d00d..0000000000000 --- a/vendor/github.com/shurcooL/httpgzip/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -sudo: false -language: go -go: - - 1.x - - master -matrix: - allow_failures: - - go: master - fast_finish: true -install: - - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step). -script: - - go get -t -v ./... - - diff -u <(echo -n) <(gofmt -d -s .) - - go vet ./... - - go test -v -race ./... diff --git a/vendor/github.com/shurcooL/httpgzip/LICENSE b/vendor/github.com/shurcooL/httpgzip/LICENSE deleted file mode 100644 index 27a767b1f53d2..0000000000000 --- a/vendor/github.com/shurcooL/httpgzip/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016 Dmitri Shuralyov - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/shurcooL/httpgzip/README.md b/vendor/github.com/shurcooL/httpgzip/README.md deleted file mode 100644 index a38c2565e2859..0000000000000 --- a/vendor/github.com/shurcooL/httpgzip/README.md +++ /dev/null @@ -1,19 +0,0 @@ -httpgzip -======== - -[![Build Status](https://travis-ci.org/shurcooL/httpgzip.svg?branch=master)](https://travis-ci.org/shurcooL/httpgzip) [![GoDoc](https://godoc.org/github.com/shurcooL/httpgzip?status.svg)](https://godoc.org/github.com/shurcooL/httpgzip) - -Package httpgzip provides net/http-like primitives -that use gzip compression when serving HTTP requests. - -Installation ------------- - -```bash -go get -u github.com/shurcooL/httpgzip -``` - -License -------- - -- [MIT License](LICENSE) diff --git a/vendor/github.com/shurcooL/httpgzip/doc.go b/vendor/github.com/shurcooL/httpgzip/doc.go deleted file mode 100644 index ee3bb44f30686..0000000000000 --- a/vendor/github.com/shurcooL/httpgzip/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package httpgzip provides net/http-like primitives -// that use gzip compression when serving HTTP requests. -package httpgzip diff --git a/vendor/github.com/shurcooL/httpgzip/fs.go b/vendor/github.com/shurcooL/httpgzip/fs.go deleted file mode 100644 index 33f16a309af21..0000000000000 --- a/vendor/github.com/shurcooL/httpgzip/fs.go +++ /dev/null @@ -1,226 +0,0 @@ -package httpgzip - -import ( - "fmt" - "html" - "net/http" - "net/url" - "os" - pathpkg "path" - "sort" - "strings" - "time" -) - -// FileServer returns a handler that serves HTTP requests -// with the contents of the file system rooted at root. -// Additional optional behaviors can be controlled via opt. -func FileServer(root http.FileSystem, opt FileServerOptions) http.Handler { - if opt.ServeError == nil { - opt.ServeError = defaults.ServeError - } - return &fileServer{root: root, opt: opt} -} - -var defaults = FileServerOptions{ - ServeError: NonSpecific, -} - -// FileServerOptions specifies options for FileServer. -type FileServerOptions struct { - // IndexHTML controls special handling of "index.html" file. - IndexHTML bool - - // ServeError is used to serve errors coming from underlying file system. - // If called, it's guaranteed to be before anything has been written - // to w by FileServer, so it's safe to use http.Error. - // If nil, then NonSpecific is used. - ServeError func(w http.ResponseWriter, req *http.Request, err error) -} - -var ( - // NonSpecific serves a non-specific HTTP error message and status code - // for a given non-nil error value. It's important that NonSpecific does not - // actually include err.Error(), since its goal is to not leak information - // in error messages to users. - NonSpecific = func(w http.ResponseWriter, req *http.Request, err error) { - switch { - case os.IsNotExist(err): - http.Error(w, "404 Not Found", http.StatusNotFound) - case os.IsPermission(err): - http.Error(w, "403 Forbidden", http.StatusForbidden) - default: - http.Error(w, "500 Internal Server Error", http.StatusInternalServerError) - } - } - - // Detailed serves detailed HTTP error message and status code for a given - // non-nil error value. Because err.Error() is displayed to users, it should - // be used in development only, or if you're confident there won't be sensitive - // information in the underlying file system error messages. - Detailed = func(w http.ResponseWriter, req *http.Request, err error) { - switch { - case os.IsNotExist(err): - http.Error(w, "404 Not Found\n\n"+err.Error(), http.StatusNotFound) - case os.IsPermission(err): - http.Error(w, "403 Forbidden\n\n"+err.Error(), http.StatusForbidden) - default: - http.Error(w, "500 Internal Server Error\n\n"+err.Error(), http.StatusInternalServerError) - } - } -) - -type fileServer struct { - root http.FileSystem - opt FileServerOptions -} - -func (fs *fileServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { - if req.Method != "GET" { - w.Header().Set("Allow", "GET") - http.Error(w, "405 Method Not Allowed\n\nmethod should be GET", http.StatusMethodNotAllowed) - return - } - - // Already cleaned by net/http.cleanPath, but caller can be some middleware. - path := pathpkg.Clean("/" + req.URL.Path) - - if fs.opt.IndexHTML { - // Redirect .../index.html to .../. - // Can't use Redirect() because that would make the path absolute, - // which would be a problem running under StripPrefix. - if strings.HasSuffix(path, "/index.html") { - localRedirect(w, req, ".") - return - } - } - - f, err := fs.root.Open(path) - if err != nil { - fs.opt.ServeError(w, req, err) - return - } - defer f.Close() - fi, err := f.Stat() - if err != nil { - fs.opt.ServeError(w, req, err) - return - } - - // Redirect to canonical path: / at end of directory url. - url := req.URL.Path - if fi.IsDir() { - if !strings.HasSuffix(url, "/") && url != "" { - localRedirect(w, req, pathpkg.Base(url)+"/") - return - } - } else { - if strings.HasSuffix(url, "/") && url != "/" { - localRedirect(w, req, "../"+pathpkg.Base(url)) - return - } - } - - if fs.opt.IndexHTML { - // Use contents of index.html for directory, if present. - if fi.IsDir() { - indexPath := pathpkg.Join(path, "index.html") - f0, err := fs.root.Open(indexPath) - if err == nil { - defer f0.Close() - fi0, err := f0.Stat() - if err == nil { - path = indexPath - f = f0 - fi = fi0 - } - } - } - } - - // A directory? - if fi.IsDir() { - if checkLastModified(w, req, fi.ModTime()) { - return - } - err := dirList(w, f, path == "/") - if err != nil { - fs.opt.ServeError(w, req, err) - } - return - } - - ServeContent(w, req, fi.Name(), fi.ModTime(), f) -} - -func dirList(w http.ResponseWriter, f http.File, root bool) error { - dirs, err := f.Readdir(0) - if err != nil { - return fmt.Errorf("error reading directory: %v", err) - } - sort.Sort(byName(dirs)) - - w.Header().Set("Content-Type", "text/html; charset=utf-8") - fmt.Fprintln(w, "
")
-	switch root {
-	case true:
-		fmt.Fprintln(w, `.`)
-	case false:
-		fmt.Fprintln(w, `..`)
-	}
-	for _, d := range dirs {
-		name := d.Name()
-		if d.IsDir() {
-			name += "/"
-		}
-		// name may contain '?' or '#', which must be escaped to remain
-		// part of the URL path, and not indicate the start of a query
-		// string or fragment.
-		url := url.URL{Path: name}
-		fmt.Fprintf(w, "%s\n", url.String(), html.EscapeString(name))
-	}
-	fmt.Fprintln(w, "
") - return nil -} - -// localRedirect gives a Moved Permanently response. -// It does not convert relative paths to absolute paths like http.Redirect does. -func localRedirect(w http.ResponseWriter, req *http.Request, newPath string) { - if req.URL.RawQuery != "" { - newPath += "?" + req.URL.RawQuery - } - w.Header().Set("Location", newPath) - w.WriteHeader(http.StatusMovedPermanently) -} - -var unixEpochTime = time.Unix(0, 0) - -// modTime is the modification time of the resource to be served, or IsZero(). -// return value is whether this request is now complete. -func checkLastModified(w http.ResponseWriter, req *http.Request, modTime time.Time) bool { - if modTime.IsZero() || modTime.Equal(unixEpochTime) { - // If the file doesn't have a modTime (IsZero), or the modTime - // is obviously garbage (Unix time == 0), then ignore modtimes - // and don't process the If-Modified-Since header. - return false - } - - // The Date-Modified header truncates sub-second precision, so - // use mtime < t+1s instead of mtime <= t to check for unmodified. - if t, err := time.Parse(http.TimeFormat, req.Header.Get("If-Modified-Since")); err == nil && modTime.Before(t.Add(1*time.Second)) { - h := w.Header() - delete(h, "Content-Type") - delete(h, "Content-Length") - w.WriteHeader(http.StatusNotModified) - return true - } - w.Header().Set("Last-Modified", modTime.UTC().Format(http.TimeFormat)) - return false -} - -// byName implements sort.Interface. -type byName []os.FileInfo - -func (s byName) Len() int { return len(s) } -func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() } -func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } diff --git a/vendor/github.com/shurcooL/httpgzip/gzip.go b/vendor/github.com/shurcooL/httpgzip/gzip.go deleted file mode 100644 index cd8ab9473d96e..0000000000000 --- a/vendor/github.com/shurcooL/httpgzip/gzip.go +++ /dev/null @@ -1,115 +0,0 @@ -package httpgzip - -import ( - "bytes" - "compress/gzip" - "fmt" - "io" - "mime" - "net/http" - "path/filepath" - "time" - - "golang.org/x/net/http/httpguts" -) - -// GzipByter is implemented by compressed files for -// efficient direct access to the internal compressed bytes. -type GzipByter interface { - // GzipBytes returns gzip compressed contents of the file. - GzipBytes() []byte -} - -// NotWorthGzipCompressing is implemented by files that were determined -// not to be worth gzip compressing (the file size did not decrease as a result). -type NotWorthGzipCompressing interface { - // NotWorthGzipCompressing is a noop. It's implemented in order to indicate - // the file is not worth gzip compressing. - NotWorthGzipCompressing() -} - -// ServeContent is like http.ServeContent, except it applies gzip compression -// if compression hasn't already been done (i.e., the "Content-Encoding" header is set). -// It's aware of GzipByter and NotWorthGzipCompressing interfaces, and uses them -// to improve performance when the provided content implements them. Otherwise, -// it applies gzip compression on the fly, if it's found to be beneficial. -func ServeContent(w http.ResponseWriter, req *http.Request, name string, modTime time.Time, content io.ReadSeeker) { - // If compression has already been dealt with, serve as is. - if _, ok := w.Header()["Content-Encoding"]; ok { - http.ServeContent(w, req, name, modTime, content) - return - } - - // If request doesn't accept gzip encoding, serve without compression. - if !httpguts.HeaderValuesContainsToken(req.Header["Accept-Encoding"], "gzip") { - http.ServeContent(w, req, name, modTime, content) - return - } - - // If the file is not worth gzip compressing, serve it as is. - if _, ok := content.(NotWorthGzipCompressing); ok { - w.Header()["Content-Encoding"] = nil - http.ServeContent(w, req, name, modTime, content) - return - } - - // The following cases involve compression, so we want to detect the Content-Type eagerly, - // before passing it off to http.ServeContent. It's because http.ServeContent won't be able - // to easily detect the original content type after content has been gzip compressed. - // We do this even for the last case that serves uncompressed data so that it doesn't - // have to do duplicate work. - _, haveType := w.Header()["Content-Type"] - if !haveType { - ctype := mime.TypeByExtension(filepath.Ext(name)) - if ctype == "" { - // Read a chunk to decide between utf-8 text and binary. - var buf [512]byte - n, _ := io.ReadFull(content, buf[:]) - ctype = http.DetectContentType(buf[:n]) - _, err := content.Seek(0, io.SeekStart) // Rewind to output whole file. - if err != nil { - http.Error(w, "500 Internal Server Error\n\nseeker can't seek", http.StatusInternalServerError) - return - } - } - w.Header().Set("Content-Type", ctype) - } - - // If there are gzip encoded bytes available, use them directly. - if gzipFile, ok := content.(GzipByter); ok { - w.Header().Set("Content-Encoding", "gzip") - http.ServeContent(w, req, name, modTime, bytes.NewReader(gzipFile.GzipBytes())) - return - } - - // Perform compression and serve gzip compressed bytes (if it's worth it). - if rs, err := gzipCompress(content); err == nil { - w.Header().Set("Content-Encoding", "gzip") - http.ServeContent(w, req, name, modTime, rs) - return - } - - // Serve as is. - w.Header()["Content-Encoding"] = nil - http.ServeContent(w, req, name, modTime, content) -} - -// gzipCompress compresses input from r and returns it as an io.ReadSeeker. -// It returns an error if compressed size is not smaller than uncompressed. -func gzipCompress(r io.Reader) (io.ReadSeeker, error) { - var buf bytes.Buffer - gw := gzip.NewWriter(&buf) - n, err := io.Copy(gw, r) - if err != nil { - // No need to gw.Close() here since we're discarding the result, and gzip.Writer.Close isn't needed for cleanup. - return nil, err - } - err = gw.Close() - if err != nil { - return nil, err - } - if int64(buf.Len()) >= n { - return nil, fmt.Errorf("not worth gzip compressing: original size %v, compressed size %v", n, buf.Len()) - } - return bytes.NewReader(buf.Bytes()), nil -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 55346df76086a..a30151bf342be 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -683,9 +683,6 @@ github.com/sergi/go-diff/diffmatchpatch # github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 ## explicit github.com/shurcooL/httpfs/vfsutil -# github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0 -## explicit -github.com/shurcooL/httpgzip # github.com/shurcooL/sanitized_anchor_name v1.0.0 github.com/shurcooL/sanitized_anchor_name # github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 From 14966084d28f15a14f4cf75a69548c15f17ddfe1 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 23 Dec 2020 22:57:53 +0800 Subject: [PATCH 3/6] Add tests for parseAcceptEncoding --- modules/public/public.go | 2 +- modules/public/public_test.go | 40 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 modules/public/public_test.go diff --git a/modules/public/public.go b/modules/public/public.go index 37eceef345e41..d038290d37f9c 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -92,7 +92,7 @@ func parseAcceptEncoding(val string) map[string]bool { parts := strings.Split(val, ";") var types = make(map[string]bool) for _, v := range strings.Split(parts[0], ",") { - types[v] = true + types[strings.TrimSpace(v)] = true } return types } diff --git a/modules/public/public_test.go b/modules/public/public_test.go new file mode 100644 index 0000000000000..cf8dced431146 --- /dev/null +++ b/modules/public/public_test.go @@ -0,0 +1,40 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package public + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseAcceptEncoding(t *testing.T) { + var kases = []struct { + Header string + Expected map[string]bool + }{ + { + Header: "deflate, gzip;q=1.0, *;q=0.5", + Expected: map[string]bool{ + "deflate": true, + "gzip": true, + }, + }, + { + Header: " gzip, deflate, br", + Expected: map[string]bool{ + "deflate": true, + "gzip": true, + "br": true, + }, + }, + } + + for _, kase := range kases { + t.Run(kase.Header, func(t *testing.T) { + assert.EqualValues(t, kase.Expected, parseAcceptEncoding(kase.Header)) + }) + } +} From 19bf03ce57566410468af959ee5e8f37d7eda8c0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 23 Dec 2020 23:26:53 +0800 Subject: [PATCH 4/6] Update docs for ENABLE_GZIP --- custom/conf/app.example.ini | 2 +- docs/content/doc/advanced/config-cheat-sheet.en-us.md | 2 +- docs/content/doc/advanced/config-cheat-sheet.zh-cn.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index b89bbf894e500..e26e9e4d5690e 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -361,7 +361,7 @@ KEY_FILE = https/key.pem STATIC_ROOT_PATH = ; Default path for App data APP_DATA_PATH = data -; Application level GZIP support +; Enable gzip compression for runtime-generated content, static resources excluded ENABLE_GZIP = false ; Application profiling (memory and cpu) ; For "web" command it listens on localhost:6060 diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index d482523f797bc..43f42b95e02f1 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -264,7 +264,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `STATIC_ROOT_PATH`: **./**: Upper level of template and static files path. - `APP_DATA_PATH`: **data** (**/data/gitea** on docker): Default path for application data. - `STATIC_CACHE_TIME`: **6h**: Web browser cache time for static resources on `custom/`, `public/` and all uploaded avatars. Note that this cache is disabled when `RUN_MODE` is "dev". -- `ENABLE_GZIP`: **false**: Enables application-level GZIP support. +- `ENABLE_GZIP`: **false**: Enable gzip compression for runtime-generated content, static resources excluded. - `ENABLE_PPROF`: **false**: Application profiling (memory and cpu). For "web" command it listens on localhost:6060. For "serv" command it dumps to disk at `PPROF_DATA_PATH` as `(cpuprofile|memprofile)__` - `PPROF_DATA_PATH`: **data/tmp/pprof**: `PPROF_DATA_PATH`, use an absolute path when you start gitea as service - `LANDING_PAGE`: **home**: Landing page for unauthenticated users \[home, explore, organizations, login\]. diff --git a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md index c1f7e836c01a8..da2d02c11dcca 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md +++ b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md @@ -70,7 +70,7 @@ menu: - `KEY_FILE`: 启用HTTPS的密钥文件。 - `STATIC_ROOT_PATH`: 存放模板和静态文件的根目录,默认是 Gitea 的根目录。 - `STATIC_CACHE_TIME`: **6h**: 静态资源文件,包括 `custom/`, `public/` 和所有上传的头像的浏览器缓存时间。 -- `ENABLE_GZIP`: 启用应用级别的 GZIP 压缩。 +- `ENABLE_GZIP`: 启用实时生成的数据启用 GZIP 压缩,不包括静态资源。 - `LANDING_PAGE`: 未登录用户的默认页面,可选 `home` 或 `explore`。 - `LFS_START_SERVER`: 是否启用 git-lfs 支持. 可以为 `true` 或 `false`, 默认是 `false`。 From 4d27b43fb35d786eb1f1204fc24c2efb53990439 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 23 Dec 2020 23:41:19 +0800 Subject: [PATCH 5/6] Fix bug --- modules/public/static.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/modules/public/static.go b/modules/public/static.go index 5a92560a5a78a..754b68e9d15fb 100644 --- a/modules/public/static.go +++ b/modules/public/static.go @@ -10,11 +10,12 @@ import ( "compress/gzip" "io" "io/ioutil" - "log" "mime" "net/http" "path/filepath" "time" + + "code.gitea.io/gitea/modules/log" ) // Static implements the macaron static handler for serving assets. @@ -60,9 +61,9 @@ func AssetIsDir(name string) (bool, error) { func ServeContent(w http.ResponseWriter, req *http.Request, name string, modtime time.Time, content io.ReadSeeker) { encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding")) if encodings["gzip"] { - if rd, ok := f.(*vfsgen۰CompressedFile); ok { + if rd, ok := content.(*vfsgen۰CompressedFile); ok { w.Header().Set("Content-Encoding", "gzip") - ctype := mime.TypeByExtension(filepath.Ext(fi.Name())) + ctype := mime.TypeByExtension(filepath.Ext(name)) if ctype == "" { // read a chunk to decide between utf-8 text and binary var buf [512]byte @@ -71,16 +72,17 @@ func ServeContent(w http.ResponseWriter, req *http.Request, name string, modtime ctype = http.DetectContentType(buf[:n]) _, err := rd.Seek(0, io.SeekStart) // rewind to output whole file if err != nil { - log.Printf("rd.Seek error: %v\n", err) - return false + log.Error("rd.Seek error: %v", err) + http.Error(w, http.StatusText(500), 500) + return } } w.Header().Set("Content-Type", ctype) - http.ServeContent(w, req, file, fi.ModTime(), rd) - return true + http.ServeContent(w, req, name, modtime, rd) + return } } - http.ServeContent(w, req, file, fi.ModTime(), f) - return true + http.ServeContent(w, req, name, modtime, content) + return } From d03cf63b75dc959d45fb2da8bb1619caba488e79 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 24 Dec 2020 00:16:12 +0800 Subject: [PATCH 6/6] Fix bug --- modules/public/dynamic.go | 5 +++-- modules/public/public.go | 2 +- modules/public/static.go | 13 ++++++++----- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/modules/public/dynamic.go b/modules/public/dynamic.go index 47c0c589e306b..f634c598a3f1a 100644 --- a/modules/public/dynamic.go +++ b/modules/public/dynamic.go @@ -9,6 +9,7 @@ package public import ( "io" "net/http" + "os" "time" ) @@ -18,6 +19,6 @@ func Static(opts *Options) func(next http.Handler) http.Handler { } // ServeContent serve http content -func ServeContent(w http.ResponseWriter, req *http.Request, name string, modtime time.Time, content io.ReadSeeker) { - http.ServeContent(w, req, name, modtime, content) +func ServeContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modtime time.Time, content io.ReadSeeker) { + http.ServeContent(w, req, fi.Name(), modtime, content) } diff --git a/modules/public/public.go b/modules/public/public.go index d038290d37f9c..c8148e6db36f3 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -167,6 +167,6 @@ func (opts *Options) handle(w http.ResponseWriter, req *http.Request, opt *Optio return true } - ServeContent(w, req, file, fi.ModTime(), f) + ServeContent(w, req, fi, fi.ModTime(), f) return true } diff --git a/modules/public/static.go b/modules/public/static.go index 754b68e9d15fb..c4dd7a1eca750 100644 --- a/modules/public/static.go +++ b/modules/public/static.go @@ -7,11 +7,13 @@ package public import ( + "bytes" "compress/gzip" "io" "io/ioutil" "mime" "net/http" + "os" "path/filepath" "time" @@ -58,12 +60,13 @@ func AssetIsDir(name string) (bool, error) { } // ServeContent serve http content -func ServeContent(w http.ResponseWriter, req *http.Request, name string, modtime time.Time, content io.ReadSeeker) { +func ServeContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modtime time.Time, content io.ReadSeeker) { encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding")) if encodings["gzip"] { - if rd, ok := content.(*vfsgen۰CompressedFile); ok { + if cf, ok := fi.(*vfsgen۰CompressedFileInfo); ok { + rd := bytes.NewReader(cf.GzipBytes()) w.Header().Set("Content-Encoding", "gzip") - ctype := mime.TypeByExtension(filepath.Ext(name)) + ctype := mime.TypeByExtension(filepath.Ext(fi.Name())) if ctype == "" { // read a chunk to decide between utf-8 text and binary var buf [512]byte @@ -78,11 +81,11 @@ func ServeContent(w http.ResponseWriter, req *http.Request, name string, modtime } } w.Header().Set("Content-Type", ctype) - http.ServeContent(w, req, name, modtime, rd) + http.ServeContent(w, req, fi.Name(), modtime, rd) return } } - http.ServeContent(w, req, name, modtime, content) + http.ServeContent(w, req, fi.Name(), modtime, content) return }