diff --git a/application/controller/common.go b/application/controller/common.go index 40ab1bd..0e7398a 100644 --- a/application/controller/common.go +++ b/application/controller/common.go @@ -20,56 +20,9 @@ package controller import ( "net/http" "strings" - "time" ) func clientSupportGZIP(r *http.Request) bool { // Should be good enough return strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") } - -func clientContentEtagIsValid(r *http.Request, eTag string) bool { - d := r.Header.Get("If-None-Match") - - if len(d) < 0 { - return false - } - - dStart := 0 - qETag := "\"" + eTag + "\"" - - for { - dIdx := strings.Index(d[dStart:], ",") - - if dIdx < 0 { - return strings.Contains(d[dStart:], qETag) || - strings.Contains(d[dStart:], "*") - } - - if strings.Contains(d[dStart:dStart+dIdx], qETag) { - return true - } - - if strings.Contains(d[dStart:dStart+dIdx], "*") { - return true - } - - dStart += dIdx + 1 - } -} - -func clientContentModifiedSince(r *http.Request, mod time.Time) bool { - d := r.Header.Get("If-Modified-Since") - - if len(d) < 0 { - return false - } - - dt, dtErr := time.Parse(time.RFC1123, d) - - if dtErr != nil { - return false - } - - return !mod.Before(dt) -} diff --git a/application/controller/common_test.go b/application/controller/common_test.go index 18a875b..9d48180 100644 --- a/application/controller/common_test.go +++ b/application/controller/common_test.go @@ -16,32 +16,3 @@ // along with this program. If not, see . package controller - -import ( - "net/http" - "testing" -) - -func TestClientContentEtagIsValid(t *testing.T) { - test := func(id int, hd []string, etag string, expected bool) { - r := http.Request{ - Header: http.Header{ - "If-None-Match": hd, - }, - } - rr := clientContentEtagIsValid(&r, etag) - - if rr != expected { - t.Errorf("Test: %d: Expecting the result to be %v, got %v instead", - id, expected, rr) - - return - } - } - - test(0, []string{""}, "test", false) - test(1, []string{"*"}, "test", true) - test(2, []string{"W/\"67ab43\", \"54ed21\", \"7892dd\""}, "54ed21", true) - test(3, []string{"\"bfc13a64729c4290ef5b2c2730249c88ca92d82d\""}, - "bfc13a64729c4290ef5b2c2730249c88ca92d82d", true) -} diff --git a/application/controller/static.go b/application/controller/static.go index 6aa319d..cb12f5f 100644 --- a/application/controller/static.go +++ b/application/controller/static.go @@ -44,11 +44,9 @@ func (s staticData) hasCompressed() bool { func staticFileExt(fileName string) string { extIdx := strings.LastIndex(fileName, ".") - if extIdx < 0 { return "" } - return strings.ToLower(fileName[extIdx:]) } @@ -62,7 +60,6 @@ func serveStaticCacheData( if fileExt == ".html" || fileExt == ".htm" { return ErrNotFound } - return serveStaticCachePage(dataName, w, r, l) } @@ -73,56 +70,26 @@ func serveStaticCachePage( l log.Logger, ) error { d, dFound := staticPages[dataName] - if !dFound { return ErrNotFound } - selectedData := d.data - selectedDataHash := d.dataHash selectedLength := len(d.data) compressEnabled := false - if clientSupportGZIP(r) && d.hasCompressed() { selectedData = d.compressd - selectedDataHash = d.compressdHash selectedLength = len(d.compressd) - compressEnabled = true - w.Header().Add("Vary", "Accept-Encoding") } - - canUseCache := true - - if !clientContentEtagIsValid(r, selectedDataHash) { - canUseCache = false - } - - if clientContentModifiedSince(r, d.created) { - canUseCache = false - } - - if canUseCache { - w.WriteHeader(http.StatusNotModified) - - return nil - } - - w.Header().Add("Cache-Control", "public, max-age=31536000") - w.Header().Add("ETag", "\""+selectedDataHash+"\"") - + w.Header().Add("Cache-Control", "public, max-age=5184000") w.Header().Add("Content-Type", d.contentType) - if compressEnabled { w.Header().Add("Content-Encoding", "gzip") } - w.Header().Add("Content-Length", strconv.FormatInt(int64(selectedLength), 10)) - _, wErr := w.Write(selectedData) - return wErr } @@ -134,36 +101,25 @@ func serveStaticPage( l log.Logger, ) error { d, dFound := staticPages[dataName] - if !dFound { return ErrNotFound } - selectedData := d.data selectedLength := len(d.data) compressEnabled := false - if clientSupportGZIP(r) && d.hasCompressed() { selectedData = d.compressd selectedLength = len(d.compressd) - compressEnabled = true - w.Header().Add("Vary", "Accept-Encoding") } - w.Header().Add("Content-Type", d.contentType) - if compressEnabled { w.Header().Add("Content-Encoding", "gzip") } - w.Header().Add("Content-Length", strconv.FormatInt(int64(selectedLength), 10)) - w.WriteHeader(code) - _, wErr := w.Write(selectedData) - return wErr } diff --git a/application/controller/static_page_generater/main.go b/application/controller/static_page_generater/main.go index 08e7a98..3e65d0a 100644 --- a/application/controller/static_page_generater/main.go +++ b/application/controller/static_page_generater/main.go @@ -20,8 +20,6 @@ package main import ( "bytes" "compress/gzip" - "crypto/sha256" - "encoding/base64" "fmt" "io" "io/ioutil" @@ -77,17 +75,13 @@ func parseStaticData( fileEnd int, compressedStart int, compressedEnd int, - contentHash string, - compressedHash string, creation time.Time, data []byte, contentType string, ) staticData { return staticData{ data: data[fileStart:fileEnd], - dataHash: contentHash, compressd: data[compressedStart:compressedEnd], - compressdHash: compressedHash, created: creation, contentType: contentType, } @@ -98,9 +92,7 @@ func parseStaticData( import "bytes" import "fmt" import "compress/gzip" -import "encoding/base64" import "time" -import "crypto/sha256" import "mime" import "strings" @@ -111,10 +103,8 @@ func getMimeTypeByExtension(ext string) string { switch ext { case ".ico": return "image/x-icon" - case ".md": return "text/markdown" - default: return mime.TypeByExtension(ext) } @@ -122,64 +112,38 @@ func getMimeTypeByExtension(ext string) string { func staticFileGen(fileName, filePath string) staticData { content, readErr := ioutil.ReadFile(filePath) - if readErr != nil { panic(fmt.Sprintln("Cannot read file:", readErr)) } - compressed := bytes.NewBuffer(make([]byte, 0, 1024)) - compresser, compresserBuildErr := gzip.NewWriterLevel( compressed, gzip.BestSpeed) - if compresserBuildErr != nil { panic(fmt.Sprintln("Cannot build data compresser:", compresserBuildErr)) } - contentLen := len(content) - _, compressErr := compresser.Write(content) - if compressErr != nil { panic(fmt.Sprintln("Cannot write compressed data:", compressErr)) } - compressErr = compresser.Flush() - if compressErr != nil { panic(fmt.Sprintln("Cannot write compressed data:", compressErr)) } - content = append(content, compressed.Bytes()...) - - getHash := func(b []byte) []byte { - h := sha256.New() - h.Write(b) - - return h.Sum(nil) - } - fileExtDotIdx := strings.LastIndex(fileName, ".") fileExt := "" - if fileExtDotIdx >= 0 { fileExt = fileName[fileExtDotIdx:len(fileName)] } - mimeType := getMimeTypeByExtension(fileExt) - if len(mimeType) <= 0 { mimeType = "application/binary" } - return staticData{ data: content[0:contentLen], contentType: mimeType, - dataHash: base64.StdEncoding.EncodeToString( - getHash(content[0:contentLen])[:8]), compressd: content[contentLen:], - compressdHash: base64.StdEncoding.EncodeToString( - getHash(content[contentLen:])[:8]), created: time.Now(), } } @@ -217,22 +181,17 @@ func {{ .GOVariableName }}() ( int, // FileEnd int, // CompressedStart int, // CompressedEnd - string, // ContentHash - string, // CompressedHash time.Time, // Time of creation []byte, // Data string, // ContentType ) { created, createErr := time.Parse( time.RFC1123, "{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 MST" }}") - if createErr != nil { panic(createErr) } - return {{ .FileStart }}, {{ .FileEnd }}, {{ .CompressedStart }}, {{ .CompressedEnd }}, - "{{ .ContentHash }}", "{{ .CompressedHash }}", created, []byte({{ .Data }}), "{{ .ContentType }}" } ` @@ -255,36 +214,24 @@ type parsedFile struct { CompressedStart int CompressedEnd int ContentType string - ContentHash string - CompressedHash string Date time.Time } -func getHash(b []byte) []byte { - h := sha256.New() - h.Write(b) - - return h.Sum(nil) -} - func buildListFile(w io.Writer, data interface{}) error { tpl := template.Must(template.New( "StaticPageList").Parse(staticListTemplate)) - return tpl.Execute(w, data) } func buildListFileDev(w io.Writer, data interface{}) error { tpl := template.Must(template.New( "StaticPageList").Parse(staticListTemplateDev)) - return tpl.Execute(w, data) } func buildDataFile(w io.Writer, data interface{}) error { tpl := template.Must(template.New( "StaticPageData").Parse(staticPageTemplate)) - return tpl.Execute(w, data) } @@ -296,22 +243,16 @@ func getMimeTypeByExtension(ext string) string { switch ext { case ".ico": return "image/x-icon" - case ".md": return "text/markdown" - case ".map": return "text/plain" - case ".txt": return "text/plain" - case ".woff": return "application/font-woff" - case ".woff2": return "application/font-woff2" - default: return mime.TypeByExtension(ext) } @@ -320,26 +261,19 @@ func getMimeTypeByExtension(ext string) string { func parseFile( id int, name string, filePath string, packageName string) parsedFile { content, readErr := ioutil.ReadFile(filePath) - if readErr != nil { panic(fmt.Sprintln("Cannot read file:", readErr)) } - contentLen := len(content) - fileExtDotIdx := strings.LastIndex(name, ".") fileExt := "" - if fileExtDotIdx >= 0 { fileExt = name[fileExtDotIdx:] } - mimeType := getMimeTypeByExtension(fileExt) - if len(mimeType) <= 0 { mimeType = "application/binary" } - if strings.HasPrefix(mimeType, "image/") { // Don't compress images } else if strings.HasPrefix(mimeType, "application/font-woff") { @@ -348,32 +282,23 @@ func parseFile( // Don't compress plain text } else { compressed := bytes.NewBuffer(make([]byte, 0, 1024)) - compresser, compresserBuildErr := gzip.NewWriterLevel( compressed, gzip.BestCompression) - if compresserBuildErr != nil { panic(fmt.Sprintln( "Cannot build data compresser:", compresserBuildErr)) } - _, compressErr := compresser.Write(content) - if compressErr != nil { panic(fmt.Sprintln("Cannot write compressed data:", compressErr)) } - compressErr = compresser.Flush() - if compressErr != nil { panic(fmt.Sprintln("Cannot write compressed data:", compressErr)) } - content = append(content, compressed.Bytes()...) } - goFileName := "Static" + strconv.FormatInt(int64(id), 10) - return parsedFile{ Name: name, GOVariableName: strings.Title(goFileName), @@ -386,11 +311,7 @@ func parseFile( CompressedStart: contentLen, CompressedEnd: len(content), ContentType: mimeType, - ContentHash: base64.StdEncoding.EncodeToString( - getHash(content[0:contentLen])[:8]), - CompressedHash: base64.StdEncoding.EncodeToString( - getHash(content[contentLen:])[:8]), - Date: time.Now(), + Date: time.Now(), } } @@ -398,126 +319,92 @@ func main() { if len(os.Args) < 3 { panic("Usage: <(Destination) List File>") } - sourcePath, sourcePathErr := filepath.Abs(os.Args[1]) - if sourcePathErr != nil { panic(fmt.Sprintf("Invalid source folder path %s: %s", os.Args[1], sourcePathErr)) } - listFilePath, listFilePathErr := filepath.Abs(os.Args[2]) - if listFilePathErr != nil { panic(fmt.Sprintf("Invalid destination list file path %s: %s", os.Args[2], listFilePathErr)) } - listFileName := filepath.Base(listFilePath) destFolderPackage := strings.TrimSuffix( listFileName, filepath.Ext(listFileName)) destFolderPath := filepath.Join( filepath.Dir(listFilePath), destFolderPackage) - destFolderPathErr := os.RemoveAll(destFolderPath) - if destFolderPathErr != nil { panic(fmt.Sprintf("Unable to remove data destination folder %s: %s", destFolderPath, destFolderPathErr)) } - destFolderPathErr = os.Mkdir(destFolderPath, 0777) - if destFolderPathErr != nil { panic(fmt.Sprintf("Unable to build data destination folder %s: %s", destFolderPath, destFolderPathErr)) } - listFile, listFileErr := os.Create(listFilePath) - if listFileErr != nil { panic(fmt.Sprintf("Unable to open destination list file %s: %s", listFilePath, listFileErr)) } - defer listFile.Close() - files, dirOpenErr := ioutil.ReadDir(sourcePath) - if dirOpenErr != nil { panic(fmt.Sprintf("Unable to open dir: %s", dirOpenErr)) } - listFile.WriteString(staticListHeader) - listFile.WriteString("\n// This file is generated by `go generate` at " + time.Now().Format(time.RFC1123) + "\n// DO NOT EDIT!\n\n") - switch os.Getenv("NODE_ENV") { case "development": type sourceFiles struct { Name string Path string } - var sources []sourceFiles - for f := range files { if !files[f].Mode().IsRegular() { continue } - sources = append(sources, sourceFiles{ Name: files[f].Name(), Path: filepath.Join(sourcePath, files[f].Name()), }) } - tempBuildErr := buildListFileDev(listFile, sources) - if tempBuildErr != nil { panic(fmt.Sprintf( "Unable to build destination file due to error: %s", tempBuildErr)) } - default: var parsedFiles []parsedFile - for f := range files { if !files[f].Mode().IsRegular() { continue } - currentFilePath := filepath.Join(sourcePath, files[f].Name()) - parsedFiles = append(parsedFiles, parseFile( f, files[f].Name(), currentFilePath, destFolderPackage)) } - for f := range parsedFiles { fn := filepath.Join(destFolderPath, parsedFiles[f].GOFileName) - ff, ffErr := os.Create(fn) - if ffErr != nil { panic(fmt.Sprintf("Unable to create static page file %s: %s", fn, ffErr)) } - bErr := buildDataFile(ff, parsedFiles[f]) - if bErr != nil { panic(fmt.Sprintf("Unable to build static page file %s: %s", fn, bErr)) } } - listFile.WriteString( "\nimport \"" + parentPackage + "/" + destFolderPackage + "\"\n") - tempBuildErr := buildListFile(listFile, parsedFiles) - if tempBuildErr != nil { panic(fmt.Sprintf( "Unable to build destination file due to error: %s",