fasthttp-rendering-jet-template
//globals
import(
"github.com/CloudyKit/jet"
"github.com/patrickmn/go-cache"
"github.com/tdewolff/minify"
......
)
var myCache = cache.New(30 * time.Minute, time.Minute)
var myCompress = NewCompress()
var myConfig Config
var views = jet.NewHTMLSet("./views")
//fasthttp handler
func home(ctx *fasthttp.RequestCtx) {
locale:=GetUserLocale(ctx)
ck := fmt.Sprintf("%s_home", locale) //ck: cache key
if RenderCache(ctx, ck) {
return
}
p := Page{
Locale: locale,
Title: LS(locale, "Home"), //LS: func to get translated string
}
......
m := jet.VarMap{}
m.Set("LS", LS) //use LS in jet template
m.Set("news", GetPostList(co, "news", 10))
m.Set("products", GetPostList(co, "products", 10))
......
Render(ctx, "public/home", ck, m, p)
}
func Render(ctx *fasthttp.RequestCtx, t, ck string, m jet.VarMap, d interface{}) {
vw, err := views.GetTemplate(t)
if err != nil {
RenderError(ctx, "Template error:" + err.Error())
return
}
ctx.SetContentType("text/html")
cache := Cache{Type: "text/html"}
buf := bytes.Buffer{}
err = vw.Execute(&buf, m, d)
if err != nil {
RenderError(ctx, "Render error:" + err.Error())
return
}
var body []byte
if myConfig.Debug {
body = buf.Bytes()
} else {
body = myCompress.MinifyBytes("text/html", buf.Bytes())
}
gzip := strings.Contains(string(ctx.Request.Header.Peek("Accept-Encoding")), "gzip")
gziped := false
if gzip && len(body) > 512 {
zbuf := Gzip(body)
n := len(zbuf)
if n > 0 && n < len(body) {
gziped = true
ctx.Response.Header.Set("Content-Encoding", "gzip")
ctx.SetBody(zbuf)
if ck != "" {
cache.Gzip = true
cache.Body = make([]byte, n)
copy(cache.Body, zbuf)
}
}
}
if !gziped {
ctx.SetBody(body)
if ck != "" && len(body) > 0 {
cache.Body = make([]byte, len(body))
copy(cache.Body, body)
}
}
if ck != "" && len(cache.Body) > 0 {
cache.Etag = HashStr(cache.Body)
ctx.Response.Header.Set("ETag", cache.Etag)
myCache.Add(ck, cache, 30 * time.Minute)
}
VisitLog(ctx, "Render", fasthttp.StatusOK, len(ctx.Response.Body()))
}
func RenderError(ctx *fasthttp.RequestCtx, msg string) {
log.Println(msg)
if myConfig.Debug {
ctx.SetBodyString(msg)
} else {
ctx.Error("Server Error", 500)
}
}
type Cache struct {
Type string
Body []byte
Etag string
Gzip bool
}
func RenderCache(ctx *fasthttp.RequestCtx, ck string) bool {
if ck == "" || myConfig.Debug {
return false
}
cd, ok := myCache.Get(ck)
if !ok {
return false
}
ce := cd.(Cache)
etag := string(ctx.Request.Header.Peek("If-None-Match"))
if etag == ce.Etag {
ctx.NotModified()
if ce.Type == "text/html" {
VisitLog(ctx, "Cache", fasthttp.StatusNotModified, 0)
}
} else {
gzip := strings.Contains(string(ctx.Request.Header.Peek("Accept-Encoding")), "gzip")
if !gzip && ce.Gzip {
buf, err := Gunzip(ce.Body)
if err != nil {
RenderError(ctx, "Gunzip error: " + err.Error())
return false
}
ctx.SetBody(buf)
ctx.Response.Header.Set("Content-Length", strconv.Itoa(len(buf)))
} else {
ctx.SetBody(ce.Body)
ctx.Response.Header.Set("Content-Length", strconv.Itoa(len(ce.Body)))
if ce.Gzip {
ctx.Response.Header.Set("Content-Encoding", "gzip")
}
}
ctx.SetContentType(ce.Type)
ctx.Response.Header.Set("ETag", ce.Etag)
if ce.Type == "text/html" {
VisitLog(ctx, "Cache", fasthttp.StatusOK, len(ce.Body))
}
}
return true
}
//some funcs used in Render func
type Compress struct {
minifier *minify.M
}
func NewCompress() *Compress {
m := minify.New()
m.AddFunc("text/css", css.Minify)
m.AddFunc("text/html", html.Minify)
m.AddFunc("text/javascript", js.Minify)
m.AddFunc("image/svg+xml", svg.Minify)
m.AddFuncRegexp(regexp.MustCompile("[/+]json$"), json.Minify)
m.AddFuncRegexp(regexp.MustCompile("[/+]xml$"), xml.Minify)
return &Compress{
minifier: m,
}
}
func (c *Compress) MinifyBytes(ct string, src []byte) []byte {
dst, err := c.minifier.Bytes(ct, src)
if err == nil {
return dst
}
return src
}
import "github.com/klauspost/compress/gzip" //this is faster than the default gzip
func Gzip(data []byte) []byte {
var buf bytes.Buffer
w := gzip.NewWriter(&buf)
defer w.Close()
w.Write(data)
w.Flush()
return buf.Bytes()
}
func Gunzip(data []byte) ([]byte, error) {
buf := bytes.NewBuffer(data)
r, err := gzip.NewReader(buf)
if err != nil {
return nil, err
}
defer r.Close()
ud, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
return ud, nil
}
import "github.com/OneOfOne/xxhash" //xxhash is so-called the fastest hash
func Hash(data []byte) uint64 {
return xxhash.Checksum64(data)
}
func HashStr(data []byte) string {
return fmt.Sprintf("%x", Hash(data))[2:]
}