Skip to content

Commit

Permalink
Add ttf mime type
Browse files Browse the repository at this point in the history
  • Loading branch information
Litemn committed May 21, 2024
1 parent 18ea586 commit f90b31d
Showing 1 changed file with 158 additions and 156 deletions.
314 changes: 158 additions & 156 deletions cmd/frontend/main.go
Original file line number Diff line number Diff line change
@@ -1,173 +1,175 @@
package main

import (
"embed"
"errors"
"github.com/zeebo/xxh3"
"io/fs"
"log"
"net/http"
"path/filepath"
"strconv"
"strings"
"embed"
"errors"
"github.com/zeebo/xxh3"
"io/fs"
"log"
"net/http"
"path/filepath"
"strconv"
"strings"
)

var (
//go:embed all:resources
assetFs embed.FS
//go:embed all:resources
assetFs embed.FS
)

func main() {
err := run()
if err != nil {
log.Fatal(err)
}
err := run()
if err != nil {
log.Fatal(err)
}
}

type assetInfo struct {
data []byte
dataBr []byte
eTag string
contentLength string
contentLengthBr string
data []byte
dataBr []byte
eTag string
contentLength string
contentLengthBr string

contentType string
contentType string
}

func run() error {
var pathToAsset = map[string]*assetInfo{}

err := fs.WalkDir(assetFs, "resources", func(path string, _ fs.DirEntry, err error) error {
if err != nil {
return err
}

if !strings.ContainsRune(path, '.') || strings.HasPrefix(path, "/.") {
// is a directory
return nil
}

data, err := assetFs.ReadFile(path)
if err != nil {
return err
}

if path == "resources" {
return nil
}

key := strings.TrimPrefix(path, "resources")
isBr := strings.HasSuffix(key, ".br")
if isBr {
key = strings.TrimSuffix(key, ".br")
}

info := pathToAsset[key]
if info == nil {
info = &assetInfo{}
pathToAsset[key] = info
}

lengthAsString := strconv.Itoa(len(data))
if isBr {
info.dataBr = data
info.contentLengthBr = lengthAsString
} else {
info.data = data
info.contentLength = lengthAsString

// no mime type on distroless image
switch {
case strings.HasSuffix(key, ".woff"):
info.contentType = "font/woff"
case strings.HasSuffix(key, ".woff2"):
info.contentType = "font/woff2"
case strings.HasSuffix(key, ".svg"):
info.contentType = "image/svg+xml"
case strings.HasSuffix(key, ".js"):
info.contentType = "text/javascript"
case strings.HasSuffix(key, ".json"):
info.contentType = "application/json"
case strings.HasSuffix(key, ".html"):
info.contentType = "text/html"
case strings.HasSuffix(key, ".css"):
info.contentType = "text/css"
case strings.HasSuffix(key, ".wasm"):
info.contentType = "application/wasm"
case strings.HasSuffix(key, ".dictionary"):
info.contentType = "application/octet-stream"
default:
return errors.New("cannot determinate content-type by file extension: " + filepath.Ext(path))
}

// assets are immutable
if !strings.HasPrefix(key, "/assets/") {
hash := xxh3.Hash128(data)
info.eTag = strconv.FormatUint(hash.Hi, 36) + "-" + strconv.FormatUint(hash.Lo, 36)
}
}

return nil
})
if err != nil {
return err
}

// no need to keep it anymore
assetFs = embed.FS{}

indexHtml := pathToAsset["/index.html"]
pathToAsset["/"] = indexHtml

http.Handle("/index.html", http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
newPath := "./"
q := request.URL.RawQuery
if q != "" {
newPath += "?" + q
}
writer.Header().Set("Location", newPath)
writer.WriteHeader(http.StatusMovedPermanently)
}))
http.Handle("/", http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
header := writer.Header()

path := request.URL.Path
asset := pathToAsset[path]
if asset == nil {
if strings.ContainsRune(path, '.') {
http.NotFound(writer, request)
return
}

// vue router HTML5 mode (https://next.router.vuejs.org/guide/essentials/history-mode.html#html5-mode)
asset = indexHtml
}

header.Set("Vary", "Accept-Encoding")
header.Set("Content-Type", asset.contentType)

// https://medium.com/adobetech/an-http-caching-strategy-for-static-assets-configuring-the-server-1192452ce06a
if asset.eTag == "" {
header.Set("Cache-Control", "public,max-age=31536000,immutable")
} else {
header.Set("Cache-Control", "no-cache")

if request.Header.Get("If-None-Match") == asset.eTag {
writer.WriteHeader(http.StatusNotModified)
return
}

header.Set("ETag", asset.eTag)
}

if len(asset.dataBr) != 0 && strings.Contains(request.Header.Get("Accept-Encoding"), "br") {
header.Set("Content-Length", asset.contentLengthBr)
header.Set("Content-Encoding", "br")
_, _ = writer.Write(asset.dataBr)
} else {
header.Set("Content-Length", asset.contentLength)
_, _ = writer.Write(asset.data)
}
}))
return http.ListenAndServe(":8080", nil)
var pathToAsset = map[string]*assetInfo{}

err := fs.WalkDir(assetFs, "resources", func(path string, _ fs.DirEntry, err error) error {
if err != nil {
return err
}

if !strings.ContainsRune(path, '.') || strings.HasPrefix(path, "/.") {
// is a directory
return nil
}

data, err := assetFs.ReadFile(path)
if err != nil {
return err
}

if path == "resources" {
return nil
}

key := strings.TrimPrefix(path, "resources")
isBr := strings.HasSuffix(key, ".br")
if isBr {
key = strings.TrimSuffix(key, ".br")
}

info := pathToAsset[key]
if info == nil {
info = &assetInfo{}
pathToAsset[key] = info
}

lengthAsString := strconv.Itoa(len(data))
if isBr {
info.dataBr = data
info.contentLengthBr = lengthAsString
} else {
info.data = data
info.contentLength = lengthAsString

// no mime type on distroless image
switch {
case strings.HasSuffix(key, ".woff"):
info.contentType = "font/woff"
case strings.HasSuffix(key, ".woff2"):
info.contentType = "font/woff2"
case strings.HasSuffix(key, ".svg"):
info.contentType = "image/svg+xml"
case strings.HasSuffix(key, ".js"):
info.contentType = "text/javascript"
case strings.HasSuffix(key, ".json"):
info.contentType = "application/json"
case strings.HasSuffix(key, ".html"):
info.contentType = "text/html"
case strings.HasSuffix(key, ".css"):
info.contentType = "text/css"
case strings.HasSuffix(key, ".wasm"):
info.contentType = "application/wasm"
case strings.HasSuffix(key, ".dictionary"):
info.contentType = "application/octet-stream"
case strings.HasSuffix(key, ".ttf"):
info.contentType = "font/ttf"
default:
return errors.New("cannot determinate content-type by file extension: " + filepath.Ext(path))
}

// assets are immutable
if !strings.HasPrefix(key, "/assets/") {
hash := xxh3.Hash128(data)
info.eTag = strconv.FormatUint(hash.Hi, 36) + "-" + strconv.FormatUint(hash.Lo, 36)
}
}

return nil
})
if err != nil {
return err
}

// no need to keep it anymore
assetFs = embed.FS{}

indexHtml := pathToAsset["/index.html"]
pathToAsset["/"] = indexHtml

http.Handle("/index.html", http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
newPath := "./"
q := request.URL.RawQuery
if q != "" {
newPath += "?" + q
}
writer.Header().Set("Location", newPath)
writer.WriteHeader(http.StatusMovedPermanently)
}))
http.Handle("/", http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
header := writer.Header()

path := request.URL.Path
asset := pathToAsset[path]
if asset == nil {
if strings.ContainsRune(path, '.') {
http.NotFound(writer, request)
return
}

// vue router HTML5 mode (https://next.router.vuejs.org/guide/essentials/history-mode.html#html5-mode)
asset = indexHtml
}

header.Set("Vary", "Accept-Encoding")
header.Set("Content-Type", asset.contentType)

// https://medium.com/adobetech/an-http-caching-strategy-for-static-assets-configuring-the-server-1192452ce06a
if asset.eTag == "" {
header.Set("Cache-Control", "public,max-age=31536000,immutable")
} else {
header.Set("Cache-Control", "no-cache")

if request.Header.Get("If-None-Match") == asset.eTag {
writer.WriteHeader(http.StatusNotModified)
return
}

header.Set("ETag", asset.eTag)
}

if len(asset.dataBr) != 0 && strings.Contains(request.Header.Get("Accept-Encoding"), "br") {
header.Set("Content-Length", asset.contentLengthBr)
header.Set("Content-Encoding", "br")
_, _ = writer.Write(asset.dataBr)
} else {
header.Set("Content-Length", asset.contentLength)
_, _ = writer.Write(asset.data)
}
}))
return http.ListenAndServe(":8080", nil)
}

0 comments on commit f90b31d

Please sign in to comment.