From 41f605484e6bd07a2f846f99ebffb4ea667e5342 Mon Sep 17 00:00:00 2001 From: Mickael KERJEAN Date: Fri, 26 Oct 2018 18:22:21 +1100 Subject: [PATCH] fix (plugin): extend API for plugins --- server/common/config.go | 5 + server/common/constants.go | 1 + server/common/debug.go | 21 ++++ server/common/plugin.go | 28 ++++-- server/ctrl/files.go | 6 +- server/plugin/example/build.sh | 3 - server/plugin/example/index.go | 25 ----- server/plugin/image_heavy/index.go | 43 +++++++++ server/plugin/image_light/index.go | 149 ++++++++++++++--------------- server/plugin/index.go | 40 ++------ 10 files changed, 169 insertions(+), 152 deletions(-) create mode 100644 server/common/debug.go delete mode 100755 server/plugin/example/build.sh delete mode 100644 server/plugin/example/index.go create mode 100644 server/plugin/image_heavy/index.go diff --git a/server/common/config.go b/server/common/config.go index c4c61067..02f4bf4a 100644 --- a/server/common/config.go +++ b/server/common/config.go @@ -148,7 +148,12 @@ func (this Config) Interface() interface{} { } func (this Config) save() { + if this.path == nil { + Log.Error("Config error") + return + } if gjson.Valid(this.json) == false { + Log.Error("Config error") return } if f, err := os.OpenFile(configPath, os.O_WRONLY|os.O_CREATE, os.ModePerm); err == nil { diff --git a/server/common/constants.go b/server/common/constants.go index c4644fa5..18462937 100644 --- a/server/common/constants.go +++ b/server/common/constants.go @@ -3,6 +3,7 @@ package common const ( APP_VERSION = "v0.3" CONFIG_PATH = "data/config/" + PLUGIN_PATH = "data/plugin/" COOKIE_NAME_AUTH = "auth" COOKIE_NAME_PROOF = "proof" COOKIE_PATH = "/api/" diff --git a/server/common/debug.go b/server/common/debug.go new file mode 100644 index 00000000..0c82d594 --- /dev/null +++ b/server/common/debug.go @@ -0,0 +1,21 @@ +package common + +import ( + "runtime" + "fmt" +) + +func PrintMemUsage() { + var m runtime.MemStats + runtime.ReadMemStats(&m) + // For info on each, see: https://golang.org/pkg/runtime/#MemStats + fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc)) + fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc)) + fmt.Printf("\tSys = %v MiB", bToMb(m.Sys)) + fmt.Printf("\tObjects = %d", m.HeapObjects) + fmt.Printf("\tNumGC = %v\n", m.NumGC) +} + +func bToMb(b uint64) uint64 { + return b / 1024 / 1024 +} diff --git a/server/common/plugin.go b/server/common/plugin.go index 9b933128..04f8c0ec 100644 --- a/server/common/plugin.go +++ b/server/common/plugin.go @@ -1,11 +1,25 @@ package common -type Plugin struct { - Type string - Call interface{} - Priority int +import ( + "io" + "net/http" +) + +type Register struct{} +type Get struct{} + +var Hooks = struct { + Get Get + Register Register +}{ + Get: Get{}, + Register: Register{}, } -const ( - PROCESS_FILE_CONTENT_BEFORE_SEND = "PROCESS_FILE_CONTENT_BEFORE_SEND" -) +var process_file_content_before_send []func(io.Reader, *App, *http.ResponseWriter, *http.Request) (io.Reader, error) +func (this Register) ProcessFileContentBeforeSend(fn func(io.Reader, *App, *http.ResponseWriter, *http.Request) (io.Reader, error)) { + process_file_content_before_send = append(process_file_content_before_send, fn) +} +func (this Get) ProcessFileContentBeforeSend() []func(io.Reader, *App, *http.ResponseWriter, *http.Request) (io.Reader, error) { + return process_file_content_before_send +} diff --git a/server/ctrl/files.go b/server/ctrl/files.go index ec27a1e6..7ad2b7f6 100644 --- a/server/ctrl/files.go +++ b/server/ctrl/files.go @@ -3,7 +3,6 @@ package ctrl import ( . "github.com/mickael-kerjean/nuage/server/common" "github.com/mickael-kerjean/nuage/server/model" - "github.com/mickael-kerjean/nuage/server/plugin" "io" "net/http" "path/filepath" @@ -114,10 +113,7 @@ func FileCat(ctx App, res http.ResponseWriter, req *http.Request) { mType := GetMimeType(req.URL.Query().Get("path")) res.Header().Set("Content-Type", mType) - for _, obj := range plugin.ProcessFileContentBeforeSend() { - if obj == nil { - continue - } + for _, obj := range Hooks.Get.ProcessFileContentBeforeSend() { if file, err = obj(file, &ctx, &res, req); err != nil { SendErrorResult(res, err) return diff --git a/server/plugin/example/build.sh b/server/plugin/example/build.sh deleted file mode 100755 index 4b75ceb5..00000000 --- a/server/plugin/example/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -go build -buildmode=plugin -o ../../../dist/data/plugin/example.so index.go diff --git a/server/plugin/example/index.go b/server/plugin/example/index.go deleted file mode 100644 index 1d97c0c9..00000000 --- a/server/plugin/example/index.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - . "github.com/mickael-kerjean/nuage/server/common" - "io" - "net/http" -) - -func Register(config *Config) []Plugin { - config.Get("plugins.example.foo").Default("bar") - - return []Plugin{ - { - Type: PROCESS_FILE_CONTENT_BEFORE_SEND, // where to hook our plugin in the request lifecycle - Call: hook, // actual function we trigger - Priority: 5, // determine execution order whilst multiple plugin type - }, - } -} - -func hook(file io.Reader, ctx *App, res *http.ResponseWriter, req *http.Request) (io.Reader, error){ - Log.Debug("Plugin::Example") - Log.Debug("Conf: plugins.example.foo = '%s'", ctx.Config.Get("plugins.example.foo").String()) - return file, nil -} diff --git a/server/plugin/image_heavy/index.go b/server/plugin/image_heavy/index.go new file mode 100644 index 00000000..9321d928 --- /dev/null +++ b/server/plugin/image_heavy/index.go @@ -0,0 +1,43 @@ +package main + +import ( + "bytes" + . "github.com/mickael-kerjean/nuage/server/common" + "github.com/nfnt/resize" + "image/jpeg" + "io" + "net/http" +) + +func Init(config *Config) { + plugin_enable := config.Get("transcoder.image.enable").Default(true).Bool() + plugin_thumbsize := uint(config.Get("transcoder.image.thumbnail_size").Default(300).Int()) + + Hooks.Register.ProcessFileContentBeforeSend(func(reader io.Reader, ctx *App, res *http.ResponseWriter, req *http.Request) (io.Reader, error){ + if plugin_enable == false { + return reader, nil + } + + query := req.URL.Query() + mType := GetMimeType(query.Get("path")) + + if mType != "image/jpeg" { + return reader, nil + } else if query.Get("thumbnail") != "true" { + return reader, nil + } + + (*res).Header().Set("Cache-Control", "max-age=3600") + img, err := jpeg.Decode(reader) + if err != nil { + return reader, nil + } + if obj, ok := reader.(interface{ Close() error }); ok { + obj.Close() + } + img = resize.Resize(plugin_thumbsize, 0, img, resize.Lanczos3) + out := bytes.NewBufferString("") + jpeg.Encode(out, img, &jpeg.Options{50}) + return out, nil + }) +} diff --git a/server/plugin/image_light/index.go b/server/plugin/image_light/index.go index 478aba54..cf41e2ae 100644 --- a/server/plugin/image_light/index.go +++ b/server/plugin/image_light/index.go @@ -15,95 +15,86 @@ const ( ImageCachePath = "data/cache/image/" ) -func Register(conf *Config) []Plugin { - conf.Get("plugins.transcoder.image.enable").Default(true) +func Init(conf *Config) { + plugin_enable := conf.Get("transcoder.image.enable").Default(true).Bool() cachePath := filepath.Join(GetCurrentDir(), ImageCachePath) os.RemoveAll(cachePath) os.MkdirAll(cachePath, os.ModePerm) - return []Plugin{ - { - Type: PROCESS_FILE_CONTENT_BEFORE_SEND, // where to hook our plugin in the request lifecycle - Call: hook, // actual function we trigger - Priority: -1, // last plugin to execute - }, - } -} + Hooks.Register.ProcessFileContentBeforeSend(func (reader io.Reader, ctx *App, res *http.ResponseWriter, req *http.Request) (io.Reader, error){ + if plugin_enable == false { + return reader, nil + } -func hook(reader io.Reader, ctx *App, res *http.ResponseWriter, req *http.Request) (io.Reader, error){ - if ctx.Config.Get("plugins.transcoder.image.enable").Bool() == false { - return reader, nil - } - Log.Debug("Plugin::Image") + query := req.URL.Query() + mType := GetMimeType(query.Get("path")) - query := req.URL.Query() - mType := GetMimeType(query.Get("path")) + if strings.HasPrefix(mType, "image/") == false { + return reader, nil + } else if mType == "image/svg" { + return reader, nil + } else if mType == "image/x-icon" { + return reader, nil + } else if query.Get("thumbnail") != "true" && query.Get("size") == "" { + return reader, nil + } - if strings.HasPrefix(mType, "image/") == false { - return reader, nil - } else if mType == "image/svg" { - return reader, nil - } else if mType == "image/x-icon" { - return reader, nil - } else if query.Get("thumbnail") != "true" && query.Get("size") == "" { - return reader, nil - } + ///////////////////////// + // Specify transformation + transform := &lib.Transform{ + Temporary: GetAbsolutePath(ImageCachePath + "image_" + QuickString(10)), + Size: 300, + Crop: true, + Quality: 50, + Exif: false, + } + if query.Get("thumbnail") == "true" { + (*res).Header().Set("Cache-Control", "max-age=259200") + } else if query.Get("size") != "" { + (*res).Header().Set("Cache-Control", "max-age=600") + size, err := strconv.ParseInt(query.Get("size"), 10, 64) + if err != nil { + return reader, nil + } + transform.Size = int(size) + transform.Crop = false + transform.Quality = 90 + transform.Exif = true + } - ///////////////////////// - // Specify transformation - transform := &lib.Transform{ - Temporary: GetAbsolutePath(ImageCachePath + "image_" + QuickString(10)), - Size: 300, - Crop: true, - Quality: 50, - Exif: false, - } - if query.Get("thumbnail") == "true" { - (*res).Header().Set("Cache-Control", "max-age=259200") - } else if query.Get("size") != "" { - (*res).Header().Set("Cache-Control", "max-age=600") - size, err := strconv.ParseInt(query.Get("size"), 10, 64) + ///////////////////////////// + // Insert file in the fs + // => lower RAM usage while processing + file, err := os.OpenFile(transform.Temporary, os.O_WRONLY|os.O_CREATE, os.ModePerm) if err != nil { + return reader, NewError("Can't use filesystem", 500) + } + io.Copy(file, reader) + file.Close() + if obj, ok := reader.(interface{ Close() error }); ok { + obj.Close() + } + defer func() { + os.Remove(transform.Temporary) + }() + + ///////////////////////// + // Transcode RAW image + if lib.IsRaw(mType) { + if lib.ExtractPreview(transform) == nil { + mType = "image/jpeg" + (*res).Header().Set("Content-Type", mType) + } else { + return reader, nil + } + } + + ///////////////////////// + // Final stage: resizing + if mType != "image/jpeg" && mType != "image/png" && mType != "image/gif" && mType != "image/tiff" { return reader, nil } - transform.Size = int(size) - transform.Crop = false - transform.Quality = 90 - transform.Exif = true - } - - ///////////////////////////// - // Insert file in the fs - // => lower RAM usage while processing - file, err := os.OpenFile(transform.Temporary, os.O_WRONLY|os.O_CREATE, os.ModePerm) - if err != nil { - return reader, NewError("Can't use filesystem", 500) - } - io.Copy(file, reader) - file.Close() - if obj, ok := reader.(interface{ Close() error }); ok { - obj.Close() - } - defer func() { - os.Remove(transform.Temporary) - }() - - ///////////////////////// - // Transcode RAW image - if lib.IsRaw(mType) { - if lib.ExtractPreview(transform) == nil { - mType = "image/jpeg" - (*res).Header().Set("Content-Type", mType) - } else { - return reader, nil - } - } - - ///////////////////////// - // Final stage: resizing - if mType != "image/jpeg" && mType != "image/png" && mType != "image/gif" && mType != "image/tiff" { - return reader, nil - } - return lib.CreateThumbnail(transform) + return lib.CreateThumbnail(transform) + }) } diff --git a/server/plugin/index.go b/server/plugin/index.go index e6510171..c4ac15ce 100644 --- a/server/plugin/index.go +++ b/server/plugin/index.go @@ -1,24 +1,17 @@ package plugin import ( + . "github.com/mickael-kerjean/nuage/server/common" "os" "path/filepath" plg "plugin" - . "github.com/mickael-kerjean/nuage/server/common" - "sort" "strings" - "net/http" - "io" ) -const PluginPath = "data/plugin/" - -var plugins = make(map[string][]Plugin) - func init() { ex, _ := os.Executable() - pPath := filepath.Join(filepath.Dir(ex), PluginPath) - + pPath := filepath.Join(filepath.Dir(ex), PLUGIN_PATH) + file, err := os.Open(pPath) if err != nil { return @@ -28,7 +21,7 @@ func init() { c := NewConfig() for i:=0; i < len(files); i++ { name := files[i].Name() - if strings.HasPrefix(name, ".") == true { + if strings.HasPrefix(name, ".") { continue } p, err := plg.Open(pPath + "/" + name) @@ -36,32 +29,13 @@ func init() { Log.Warning("Can't load plugin: %s => %v", name, err) continue } - - f, err := p.Lookup("Register") + fn, err := p.Lookup("Init") if err != nil { Log.Warning("Can't register plugin: %s => %v", name, err) continue } - if obj, ok := f.(func(config *Config) []Plugin); ok { - for _, plg := range obj(c) { - plugins[plg.Type] = append(plugins[plg.Type], plg) - sort.SliceStable(plugins[plg.Type], func(i, j int) bool { - return plugins[plg.Type][i].Priority > plugins[plg.Type][j].Priority - }) - } + if obj, ok := fn.(func(config *Config)); ok { + obj(c) } } } - - -func ProcessFileContentBeforeSend() []func(io.Reader, *App, *http.ResponseWriter, *http.Request) (io.Reader, error) { - fs := plugins[PROCESS_FILE_CONTENT_BEFORE_SEND] - ret := make([]func(io.Reader, *App, *http.ResponseWriter, *http.Request) (io.Reader, error), len(fs)) - for _, p := range fs { - if f, ok := p.Call.(func(io.Reader, *App, *http.ResponseWriter, *http.Request) (io.Reader, error)); ok { - ret = append(ret, f) - } - } - return ret -} -