Add UI2 plugin type for more resource efficiency and rewrite the UI plugins to use the new type

This commit is contained in:
Jan-Lukas Else 2023-02-13 20:22:42 +01:00
parent e1914d8610
commit 812cff1941
10 changed files with 65 additions and 33 deletions

View File

@ -162,7 +162,7 @@ func (a *goBlog) apCheckMentions(p *post) {
} }
apc := a.apHttpClients[p.Blog] apc := a.apHttpClients[p.Blog]
mentions := []string{} mentions := []string{}
for _, link := range lo.Uniq(links) { for _, link := range links {
act, err := apc.Actor(context.Background(), ap.IRI(link)) act, err := apc.Actor(context.Background(), ap.IRI(link))
if err != nil || act == nil || act.Type != ap.PersonType { if err != nil || act == nil || act.Type != ap.PersonType {
continue continue

View File

@ -33,6 +33,7 @@ You need to specify the path to the plugin (remember to mount the path to your G
- `Exec` (Command that is executed in a Go routine when starting GoBlog) - see https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes#Exec - `Exec` (Command that is executed in a Go routine when starting GoBlog) - see https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes#Exec
- `Middleware` (HTTP middleware to intercept or modify HTTP requests) - see https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes#Middleware - `Middleware` (HTTP middleware to intercept or modify HTTP requests) - see https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes#Middleware
- `UI` (Modify rendered HTML) - see https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes#UI - `UI` (Modify rendered HTML) - see https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes#UI
- `UI2` (Modify rendered HTML using a goquery document which improves performance and avoids multiple HTML parsing and rendering when using multiple plugins) - see https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes#UI2
More types will be added later. Any plugin can implement multiple types, see the demo plugin as example. More types will be added later. Any plugin can implement multiple types, see the demo plugin as example.

View File

@ -3,6 +3,8 @@ package plugintypes
import ( import (
"io" "io"
"net/http" "net/http"
"github.com/PuerkitoBio/goquery"
) )
// SetApp is used to allow GoBlog set its app instance to be accessible by the plugin. // SetApp is used to allow GoBlog set its app instance to be accessible by the plugin.
@ -34,3 +36,10 @@ type UI interface {
// The renderContext provides information such as the path of the request or the blog name. // The renderContext provides information such as the path of the request or the blog name.
Render(renderContext RenderContext, rendered io.Reader, modified io.Writer) Render(renderContext RenderContext, rendered io.Reader, modified io.Writer)
} }
// UI2 plugins get called when rendering HTML.
type UI2 interface {
// The renderContext provides information such as the path of the request or the blog name.
// The document can be used to add or modify HTML.
RenderWithDocument(renderContext RenderContext, doc *goquery.Document)
}

View File

@ -55,6 +55,7 @@ func init() {
"ErrorJSON": reflect.ValueOf(requests.ErrorJSON), "ErrorJSON": reflect.ValueOf(requests.ErrorJSON),
"GzipConfig": reflect.ValueOf(requests.GzipConfig), "GzipConfig": reflect.ValueOf(requests.GzipConfig),
"HasStatusErr": reflect.ValueOf(requests.HasStatusErr), "HasStatusErr": reflect.ValueOf(requests.HasStatusErr),
"LogTransport": reflect.ValueOf(requests.LogTransport),
"MaxFollow": reflect.ValueOf(requests.MaxFollow), "MaxFollow": reflect.ValueOf(requests.MaxFollow),
"NewCookieJar": reflect.ValueOf(requests.NewCookieJar), "NewCookieJar": reflect.ValueOf(requests.NewCookieJar),
"NoFollow": reflect.ValueOf(&requests.NoFollow).Elem(), "NoFollow": reflect.ValueOf(&requests.NoFollow).Elem(),

View File

@ -27,6 +27,7 @@ package yaegiwrappers
import ( import (
"context" "context"
"database/sql" "database/sql"
"github.com/PuerkitoBio/goquery"
"go.goblog.app/app/pkgs/plugintypes" "go.goblog.app/app/pkgs/plugintypes"
"io" "io"
"net/http" "net/http"
@ -45,6 +46,7 @@ func init() {
"SetApp": reflect.ValueOf((*plugintypes.SetApp)(nil)), "SetApp": reflect.ValueOf((*plugintypes.SetApp)(nil)),
"SetConfig": reflect.ValueOf((*plugintypes.SetConfig)(nil)), "SetConfig": reflect.ValueOf((*plugintypes.SetConfig)(nil)),
"UI": reflect.ValueOf((*plugintypes.UI)(nil)), "UI": reflect.ValueOf((*plugintypes.UI)(nil)),
"UI2": reflect.ValueOf((*plugintypes.UI2)(nil)),
// interface wrapper definitions // interface wrapper definitions
"_App": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_App)(nil)), "_App": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_App)(nil)),
@ -56,6 +58,7 @@ func init() {
"_SetApp": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_SetApp)(nil)), "_SetApp": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_SetApp)(nil)),
"_SetConfig": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_SetConfig)(nil)), "_SetConfig": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_SetConfig)(nil)),
"_UI": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_UI)(nil)), "_UI": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_UI)(nil)),
"_UI2": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_UI2)(nil)),
} }
} }
@ -204,3 +207,13 @@ type _go_goblog_app_app_pkgs_plugintypes_UI struct {
func (W _go_goblog_app_app_pkgs_plugintypes_UI) Render(renderContext plugintypes.RenderContext, rendered io.Reader, modified io.Writer) { func (W _go_goblog_app_app_pkgs_plugintypes_UI) Render(renderContext plugintypes.RenderContext, rendered io.Reader, modified io.Writer) {
W.WRender(renderContext, rendered, modified) W.WRender(renderContext, rendered, modified)
} }
// _go_goblog_app_app_pkgs_plugintypes_UI2 is an interface wrapper for UI2 type
type _go_goblog_app_app_pkgs_plugintypes_UI2 struct {
IValue interface{}
WRenderWithDocument func(renderContext plugintypes.RenderContext, doc *goquery.Document)
}
func (W _go_goblog_app_app_pkgs_plugintypes_UI2) RenderWithDocument(renderContext plugintypes.RenderContext, doc *goquery.Document) {
W.WRenderWithDocument(renderContext, doc)
}

View File

@ -18,6 +18,7 @@ const (
pluginSetAppType = "setapp" pluginSetAppType = "setapp"
pluginSetConfigType = "setconfig" pluginSetConfigType = "setconfig"
pluginUiType = "ui" pluginUiType = "ui"
pluginUi2Type = "ui2"
pluginExecType = "exec" pluginExecType = "exec"
pluginMiddlewareType = "middleware" pluginMiddlewareType = "middleware"
) )
@ -32,6 +33,7 @@ func (a *goBlog) initPlugins() error {
pluginSetAppType: reflect.TypeOf((*plugintypes.SetApp)(nil)).Elem(), pluginSetAppType: reflect.TypeOf((*plugintypes.SetApp)(nil)).Elem(),
pluginSetConfigType: reflect.TypeOf((*plugintypes.SetConfig)(nil)).Elem(), pluginSetConfigType: reflect.TypeOf((*plugintypes.SetConfig)(nil)).Elem(),
pluginUiType: reflect.TypeOf((*plugintypes.UI)(nil)).Elem(), pluginUiType: reflect.TypeOf((*plugintypes.UI)(nil)).Elem(),
pluginUi2Type: reflect.TypeOf((*plugintypes.UI2)(nil)).Elem(),
pluginExecType: reflect.TypeOf((*plugintypes.Exec)(nil)).Elem(), pluginExecType: reflect.TypeOf((*plugintypes.Exec)(nil)).Elem(),
pluginMiddlewareType: reflect.TypeOf((*plugintypes.Middleware)(nil)).Elem(), pluginMiddlewareType: reflect.TypeOf((*plugintypes.Middleware)(nil)).Elem(),
}, },

View File

@ -19,11 +19,12 @@ type plugin struct {
func GetPlugin() ( func GetPlugin() (
plugintypes.SetApp, plugintypes.SetConfig, plugintypes.SetApp, plugintypes.SetConfig,
plugintypes.UI, plugintypes.UI,
plugintypes.UI2,
plugintypes.Exec, plugintypes.Exec,
plugintypes.Middleware, plugintypes.Middleware,
) { ) {
p := &plugin{} p := &plugin{}
return p, p, p, p, p return p, p, p, p, p, p
} }
// SetApp // SetApp
@ -40,7 +41,7 @@ func (p *plugin) SetConfig(config map[string]any) {
func (*plugin) Render(_ plugintypes.RenderContext, rendered io.Reader, modified io.Writer) { func (*plugin) Render(_ plugintypes.RenderContext, rendered io.Reader, modified io.Writer) {
doc, err := goquery.NewDocumentFromReader(rendered) doc, err := goquery.NewDocumentFromReader(rendered)
if err != nil { if err != nil {
fmt.Println("demoui plugin: " + err.Error()) fmt.Println("demo plugin: " + err.Error())
return return
} }
buf := bufferpool.Get() buf := bufferpool.Get()
@ -53,6 +54,17 @@ func (*plugin) Render(_ plugintypes.RenderContext, rendered io.Reader, modified
_ = goquery.Render(modified, doc.Selection) _ = goquery.Render(modified, doc.Selection)
} }
// UI
func (p *plugin) RenderWithDocument(rc plugintypes.RenderContext, doc *goquery.Document) {
buf := bufferpool.Get()
defer bufferpool.Put(buf)
hb := htmlbuilder.NewHtmlBuilder(buf)
hb.WriteElementOpen("p")
hb.WriteEscaped("Second end of post content")
hb.WriteElementClose("p")
doc.Find("main.h-entry article div.e-content").AppendHtml(buf.String())
}
// Exec // Exec
func (p *plugin) Exec() { func (p *plugin) Exec() {
fmt.Println("Hello World from the demo plugin!") fmt.Println("Hello World from the demo plugin!")

View File

@ -1,9 +1,6 @@
package syndication package syndication
import ( import (
"fmt"
"io"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
"go.goblog.app/app/pkgs/bufferpool" "go.goblog.app/app/pkgs/bufferpool"
"go.goblog.app/app/pkgs/htmlbuilder" "go.goblog.app/app/pkgs/htmlbuilder"
@ -15,7 +12,7 @@ type plugin struct {
parameterName string parameterName string
} }
func GetPlugin() (plugintypes.SetConfig, plugintypes.SetApp, plugintypes.UI) { func GetPlugin() (plugintypes.SetConfig, plugintypes.SetApp, plugintypes.UI2) {
p := &plugin{} p := &plugin{}
return p, p, p return p, p, p
} }
@ -33,24 +30,13 @@ func (p *plugin) SetConfig(config map[string]any) {
} }
} }
func (p *plugin) Render(rc plugintypes.RenderContext, rendered io.Reader, modified io.Writer) { func (p *plugin) RenderWithDocument(rc plugintypes.RenderContext, doc *goquery.Document) {
def := func() {
_, _ = io.Copy(modified, rendered)
}
post, err := p.app.GetPost(rc.GetPath()) post, err := p.app.GetPost(rc.GetPath())
if err != nil || post == nil { if err != nil || post == nil {
def()
return return
} }
syndicationLinks, ok := post.GetParameters()[p.parameterName] syndicationLinks, ok := post.GetParameters()[p.parameterName]
if !ok || len(syndicationLinks) == 0 { if !ok || len(syndicationLinks) == 0 {
def()
return
}
doc, err := goquery.NewDocumentFromReader(rendered)
if err != nil {
fmt.Println("syndication plugin: " + err.Error())
def()
return return
} }
buf := bufferpool.Get() buf := bufferpool.Get()
@ -61,5 +47,4 @@ func (p *plugin) Render(rc plugintypes.RenderContext, rendered io.Reader, modifi
hb.WriteElementClose("data") hb.WriteElementClose("data")
} }
doc.Find("main.h-entry article").AppendHtml(buf.String()) doc.Find("main.h-entry article").AppendHtml(buf.String())
_ = goquery.Render(modified, doc.Selection)
} }

View File

@ -2,7 +2,6 @@ package webrings
import ( import (
"fmt" "fmt"
"io"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
"go.goblog.app/app/pkgs/bufferpool" "go.goblog.app/app/pkgs/bufferpool"
@ -10,7 +9,7 @@ import (
"go.goblog.app/app/pkgs/plugintypes" "go.goblog.app/app/pkgs/plugintypes"
) )
func GetPlugin() (plugintypes.SetConfig, plugintypes.UI) { func GetPlugin() (plugintypes.SetConfig, plugintypes.UI2) {
p := &plugin{} p := &plugin{}
return p, p return p, p
} }
@ -23,17 +22,12 @@ func (p *plugin) SetConfig(config map[string]any) {
p.config = config p.config = config
} }
func (p *plugin) Render(rc plugintypes.RenderContext, rendered io.Reader, modified io.Writer) { func (p *plugin) RenderWithDocument(rc plugintypes.RenderContext, doc *goquery.Document) {
blog := rc.GetBlog() blog := rc.GetBlog()
if blog == "" { if blog == "" {
fmt.Println("webrings plugin: blog is empty!") fmt.Println("webrings plugin: blog is empty!")
return return
} }
doc, err := goquery.NewDocumentFromReader(rendered)
if err != nil {
fmt.Println("webrings plugin: " + err.Error())
return
}
if blogWebringsAny, ok := p.config[blog]; ok { if blogWebringsAny, ok := p.config[blog]; ok {
if blogWebrings, ok := blogWebringsAny.([]any); ok { if blogWebrings, ok := blogWebringsAny.([]any); ok {
buf := bufferpool.Get() buf := bufferpool.Get()
@ -74,7 +68,6 @@ func (p *plugin) Render(rc plugintypes.RenderContext, rendered io.Reader, modifi
} }
} }
} }
_ = goquery.Render(modified, doc.Selection)
} }
func unwrapToString(o any) (string, bool) { func unwrapToString(o any) (string, bool) {

View File

@ -4,6 +4,7 @@ import (
"io" "io"
"net/http" "net/http"
"github.com/PuerkitoBio/goquery"
"go.goblog.app/app/pkgs/contenttype" "go.goblog.app/app/pkgs/contenttype"
"go.goblog.app/app/pkgs/htmlbuilder" "go.goblog.app/app/pkgs/htmlbuilder"
"go.goblog.app/app/pkgs/plugintypes" "go.goblog.app/app/pkgs/plugintypes"
@ -45,15 +46,30 @@ func (a *goBlog) renderWithStatusCode(w http.ResponseWriter, r *http.Request, st
f(htmlbuilder.NewHtmlBuilder(renderPipeWriter), data) f(htmlbuilder.NewHtmlBuilder(renderPipeWriter), data)
_ = renderPipeWriter.Close() _ = renderPipeWriter.Close()
}() }()
// Create render context for plugins
rc := &pluginRenderContext{
blog: data.BlogString,
path: r.URL.Path,
}
// Run UI plugins // Run UI plugins
pluginPipeReader, pluginPipeWriter := io.Pipe() pluginPipeReader, pluginPipeWriter := io.Pipe()
go func() { go func() {
a.chainUiPlugins(a.getPlugins(pluginUiType), &pluginRenderContext{ a.chainUiPlugins(a.getPlugins(pluginUiType), rc, renderPipeReader, pluginPipeWriter)
blog: data.BlogString,
path: r.URL.Path,
}, renderPipeReader, pluginPipeWriter)
_ = pluginPipeWriter.Close() _ = pluginPipeWriter.Close()
}() }()
// Run UI2 plugins
ui2Plugins := a.getPlugins(pluginUi2Type)
if len(ui2Plugins) > 0 {
doc, _ := goquery.NewDocumentFromReader(pluginPipeReader)
_ = pluginPipeReader.Close()
for _, plugin := range ui2Plugins {
plugin.(plugintypes.UI2).RenderWithDocument(rc, doc)
}
pluginPipeReader, pluginPipeWriter = io.Pipe()
go func() {
_ = pluginPipeWriter.CloseWithError(goquery.Render(pluginPipeWriter, doc.Selection))
}()
}
// Return minified HTML // Return minified HTML
_ = pluginPipeReader.CloseWithError(a.min.Get().Minify(contenttype.HTML, w, pluginPipeReader)) _ = pluginPipeReader.CloseWithError(a.min.Get().Minify(contenttype.HTML, w, pluginPipeReader))
} }