Add SSR filter

This commit is contained in:
Gucheng Wang 2023-01-07 10:22:51 +08:00
parent f079778754
commit d62ec114ae
7 changed files with 330 additions and 5 deletions

View File

@ -14,4 +14,6 @@ clientSecret = xxx
jwtSecret = CasdoorSecret
casdoorOrganization = "casbin"
casdoorApplication = "app-confita"
casdoorDbName =
casdoorDbName =
cacheExpireSeconds = 60
chromeCtxNum = 1

1
go.mod
View File

@ -9,6 +9,7 @@ require (
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
github.com/casdoor/casdoor-go-sdk v0.3.3
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chromedp/chromedp v0.8.6
github.com/cristalhq/jwt/v4 v4.0.0
github.com/go-sql-driver/mysql v1.5.0
github.com/google/go-cmp v0.5.6 // indirect

24
go.sum
View File

@ -65,6 +65,12 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chromedp/cdproto v0.0.0-20220924210414-0e3390be1777 h1:nEnjcdmVQjhtQm0RFJxRINMw7lsQ8gidtbpsidiDqpY=
github.com/chromedp/cdproto v0.0.0-20220924210414-0e3390be1777/go.mod h1:5Y4sD/eXpwrChIuxhSr/G20n9CdbCmoerOHnuAf0Zr0=
github.com/chromedp/chromedp v0.8.6 h1:KobeeqR2dpfKSG1prS3Y6+FbffMmGC6xmAobRXA9QEQ=
github.com/chromedp/chromedp v0.8.6/go.mod h1:nBYHoD6YSNzrr82cIeuOzhw1Jo/s2o0QQ+ifTeoCZ+c=
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -109,6 +115,12 @@ github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gG
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
@ -186,6 +198,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@ -207,8 +221,12 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
@ -246,6 +264,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
@ -449,6 +469,7 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -456,8 +477,9 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -52,8 +52,8 @@ func main() {
//beego.DelStaticPath("/static")
beego.SetStaticPath("/static", "web/build/static")
// https://studygolang.com/articles/2303
beego.InsertFilter("/", beego.BeforeRouter, routers.TransparentStatic) // must has this for default page
beego.InsertFilter("/*", beego.BeforeRouter, routers.TransparentStatic)
beego.InsertFilter("*", beego.BeforeRouter, routers.BotFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.Static)
if conf.GetConfigString("redisEndpoint") == "" {
beego.BConfig.WebConfig.Session.SessionProvider = "file"

View File

@ -22,7 +22,7 @@ import (
"github.com/casbin/confita/util"
)
func TransparentStatic(ctx *context.Context) {
func Static(ctx *context.Context) {
urlPath := ctx.Request.URL.Path
if strings.HasPrefix(urlPath, "/api/") {
return

130
routers/filter_ssr.go Normal file
View File

@ -0,0 +1,130 @@
package routers
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"sync"
"time"
"github.com/astaxie/beego"
"github.com/astaxie/beego/context"
)
// var chromeCtx ctx.Context
var chromeCtxPool *SsrPool
var (
isChromeInstalled bool
isChromeInit bool
)
type PageCache struct {
time time.Time
html string
}
var renderCache = make(map[string]PageCache)
// modified from https://github.com/chromedp/chromedp/blob/master/allocate.go#L331
func isChromeFound() bool {
for _, path := range [...]string{
// Unix-like
"headless_shell",
"headless-shell",
"chromium",
"chromium-browser",
"google-chrome",
"google-chrome-stable",
"google-chrome-beta",
"google-chrome-unstable",
"/usr/bin/google-chrome",
// Windows
"chrome",
"chrome.exe", // in case PATHEXT is misconfigured
`C:\Program Files (x86)\Google\Chrome\Application\chrome.exe`,
`C:\Program Files\Google\Chrome\Application\chrome.exe`,
filepath.Join(os.Getenv("USERPROFILE"), `AppData\Local\Google\Chrome\Application\chrome.exe`),
// Mac
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
} {
_, err := exec.LookPath(path)
if err == nil {
// return found -> return true
// modified by Kininaru<shiftregister233@outlook.com>
return true
}
}
// return "google-chrome" -> return false
// modified by Kininaru<shiftregister233@outlook.com>
return false
}
func InitChromeDp() {
isChromeInit = true
isChromeInstalled = isChromeFound()
if isChromeInstalled {
chromeCtxNum, _ := beego.AppConfig.Int("chromeCtxNum")
if chromeCtxNum <= 0 {
chromeCtxNum = 1 // default
}
chromeCtxPool = NewSsrPool(chromeCtxNum)
}
go chromeCtxPool.Run() // start ssr_pool
}
func cacheSave(urlString string, res string) {
renderCache[urlString] = PageCache{time.Now(), res}
}
func cacheRestore(urlString string, cacheExpireSeconds int64) (string, bool) {
if _, ok := renderCache[urlString]; ok {
if time.Now().Sub(renderCache[urlString].time) < time.Duration(cacheExpireSeconds)*time.Second {
return renderCache[urlString].html, true
}
}
return "", false
}
var botRegex *regexp.Regexp
func isBot(userAgent string) bool {
if botRegex == nil {
botRegex, _ = regexp.Compile("bot|slurp|bing|crawler|spider")
}
userAgent = strings.ToLower(userAgent)
return botRegex.MatchString(userAgent)
}
func BotFilter(ctx *context.Context) {
if strings.HasPrefix(ctx.Request.URL.Path, "/api/") {
return
}
if isBot(ctx.Request.UserAgent()) {
ctx.ResponseWriter.WriteHeader(200)
urlStr := fmt.Sprintf("http://%s%s", ctx.Request.Host, ctx.Request.URL.Path)
if !isChromeInit {
InitChromeDp()
}
if !isChromeInstalled {
_, err := ctx.ResponseWriter.Write([]byte("Chrome is not installed in your server"))
if err != nil {
panic(err)
}
}
// the context will be canceled when the task send to channel
// sync.WaitGroup will wait for the task to be finished, it can avoid this problem
var wg sync.WaitGroup
wg.Add(1)
// create ssr_task and put it into task channel
task := NewRenderTask(ctx, urlStr, &wg)
chromeCtxPool.TaskChannel <- task
wg.Wait()
}
}

170
routers/ssr_pool.go Normal file
View File

@ -0,0 +1,170 @@
package routers
import (
ctx "context"
"errors"
"fmt"
"runtime"
"sync"
"time"
"github.com/astaxie/beego"
"github.com/astaxie/beego/context"
"github.com/astaxie/beego/logs"
"github.com/chromedp/chromedp"
)
var renderTimeout = 20 * time.Second
type RenderTask struct {
HttpCtx *context.Context
Url string
Render func(chromeCtx ctx.Context, url string) (string, error)
Wg *sync.WaitGroup
}
type SsrPool struct {
TaskChannel chan *RenderTask
JobsChannel chan *RenderTask
AddWorkerChannel chan bool
WorkerNum int
}
func NewRenderTask(httpCtx *context.Context, url string, wg *sync.WaitGroup) *RenderTask {
return &RenderTask{
HttpCtx: httpCtx,
Url: url,
Render: render,
Wg: wg,
}
}
func NewSsrPool(cap int) *SsrPool {
pool := SsrPool{
TaskChannel: make(chan *RenderTask),
JobsChannel: make(chan *RenderTask),
WorkerNum: cap,
}
return &pool
}
func render(chromeCtx ctx.Context, url string) (string, error) {
cacheExpireSeconds, err := beego.AppConfig.Int64("cacheExpireSeconds")
if err != nil {
return "", err
}
res, cacheHit := cacheRestore(url, cacheExpireSeconds)
if cacheHit {
return res, nil
}
// set timeout for render page
done := make(chan bool, 1)
go func() {
err = chromedp.Run(chromeCtx,
chromedp.Navigate(url),
chromedp.OuterHTML("html", &res),
)
if err != nil {
done <- false
} else {
done <- true
}
}()
select {
case success := <-done:
if success {
cacheSave(url, res)
return res, nil
} else {
return "", err
}
case <-time.After(renderTimeout):
err := chromedp.Cancel(chromeCtx)
if err != nil {
return "", errors.New("context cancel failed")
}
return "", ctx.Canceled
}
}
func (pool *SsrPool) worker() {
// chromeCtx, _ := chromedp.NewExecAllocator(ctx.Background(), append(
// chromedp.DefaultExecAllocatorOptions[:],
// chromedp.Flag("headless", false))...)
// chromeCtx, _ = chromedp.NewContext(chromeCtx)
chromeCtx, _ := chromedp.NewContext(ctx.Background()) // set default context with headless mode
for task := range pool.JobsChannel {
cancel := func() bool {
defer func() {
if err := recover(); err != nil {
handleErr(task.HttpCtx, err.(error))
task.Wg.Done()
}
}()
urlStr, err := task.Render(chromeCtx, task.Url)
if err != nil {
if err == ctx.Canceled { // when browser process has terminated
handleErr(task.HttpCtx, err)
task.Wg.Done()
return true
} else {
panic(err)
}
}
_, err = task.HttpCtx.ResponseWriter.Write([]byte(urlStr))
if err != nil {
panic(err)
}
task.Wg.Done()
return false
}()
// if canceled, break the loop
if cancel {
break
}
}
// if break, add a new worker
pool.AddWorkerChannel <- true
}
func (pool *SsrPool) Run() {
pool.AddWorkerChannel = make(chan bool, pool.WorkerNum)
for i := 0; i < pool.WorkerNum; i++ {
pool.AddWorkerChannel <- true
}
go func() {
for j := range pool.AddWorkerChannel {
if j == true {
go pool.worker()
}
}
}()
for task := range pool.TaskChannel {
pool.JobsChannel <- task
}
}
func handleErr(ctx *context.Context, err error) {
var stack string
logs.Critical("the request url is ", ctx.Input.URL())
logs.Critical("Handler crashed with error:", err)
for i := 1; ; i++ {
_, file, line, ok := runtime.Caller(i)
if !ok {
break
}
logs.Critical(fmt.Sprintf("%s:%d", file, line))
stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line))
}
if ctx.Output.Status != 0 {
ctx.ResponseWriter.WriteHeader(ctx.Output.Status)
} else {
ctx.ResponseWriter.WriteHeader(500)
}
_, _err := ctx.ResponseWriter.Write([]byte(err.(error).Error()))
if _err != nil {
logs.Critical("write response error:", err)
}
}