fix (plugin): extend API for plugins

This commit is contained in:
Mickael KERJEAN
2018-10-26 18:22:21 +11:00
parent 62acef6dee
commit 41f605484e
10 changed files with 169 additions and 152 deletions

View File

@ -148,7 +148,12 @@ func (this Config) Interface() interface{} {
} }
func (this Config) save() { func (this Config) save() {
if this.path == nil {
Log.Error("Config error")
return
}
if gjson.Valid(this.json) == false { if gjson.Valid(this.json) == false {
Log.Error("Config error")
return return
} }
if f, err := os.OpenFile(configPath, os.O_WRONLY|os.O_CREATE, os.ModePerm); err == nil { if f, err := os.OpenFile(configPath, os.O_WRONLY|os.O_CREATE, os.ModePerm); err == nil {

View File

@ -3,6 +3,7 @@ package common
const ( const (
APP_VERSION = "v0.3" APP_VERSION = "v0.3"
CONFIG_PATH = "data/config/" CONFIG_PATH = "data/config/"
PLUGIN_PATH = "data/plugin/"
COOKIE_NAME_AUTH = "auth" COOKIE_NAME_AUTH = "auth"
COOKIE_NAME_PROOF = "proof" COOKIE_NAME_PROOF = "proof"
COOKIE_PATH = "/api/" COOKIE_PATH = "/api/"

21
server/common/debug.go Normal file
View File

@ -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
}

View File

@ -1,11 +1,25 @@
package common package common
type Plugin struct { import (
Type string "io"
Call interface{} "net/http"
Priority int )
type Register struct{}
type Get struct{}
var Hooks = struct {
Get Get
Register Register
}{
Get: Get{},
Register: Register{},
} }
const ( var process_file_content_before_send []func(io.Reader, *App, *http.ResponseWriter, *http.Request) (io.Reader, error)
PROCESS_FILE_CONTENT_BEFORE_SEND = "PROCESS_FILE_CONTENT_BEFORE_SEND" 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
}

View File

@ -3,7 +3,6 @@ package ctrl
import ( import (
. "github.com/mickael-kerjean/nuage/server/common" . "github.com/mickael-kerjean/nuage/server/common"
"github.com/mickael-kerjean/nuage/server/model" "github.com/mickael-kerjean/nuage/server/model"
"github.com/mickael-kerjean/nuage/server/plugin"
"io" "io"
"net/http" "net/http"
"path/filepath" "path/filepath"
@ -114,10 +113,7 @@ func FileCat(ctx App, res http.ResponseWriter, req *http.Request) {
mType := GetMimeType(req.URL.Query().Get("path")) mType := GetMimeType(req.URL.Query().Get("path"))
res.Header().Set("Content-Type", mType) res.Header().Set("Content-Type", mType)
for _, obj := range plugin.ProcessFileContentBeforeSend() { for _, obj := range Hooks.Get.ProcessFileContentBeforeSend() {
if obj == nil {
continue
}
if file, err = obj(file, &ctx, &res, req); err != nil { if file, err = obj(file, &ctx, &res, req); err != nil {
SendErrorResult(res, err) SendErrorResult(res, err)
return return

View File

@ -1,3 +0,0 @@
#!/bin/bash
go build -buildmode=plugin -o ../../../dist/data/plugin/example.so index.go

View File

@ -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
}

View File

@ -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
})
}

View File

@ -15,95 +15,86 @@ const (
ImageCachePath = "data/cache/image/" ImageCachePath = "data/cache/image/"
) )
func Register(conf *Config) []Plugin { func Init(conf *Config) {
conf.Get("plugins.transcoder.image.enable").Default(true) plugin_enable := conf.Get("transcoder.image.enable").Default(true).Bool()
cachePath := filepath.Join(GetCurrentDir(), ImageCachePath) cachePath := filepath.Join(GetCurrentDir(), ImageCachePath)
os.RemoveAll(cachePath) os.RemoveAll(cachePath)
os.MkdirAll(cachePath, os.ModePerm) os.MkdirAll(cachePath, os.ModePerm)
return []Plugin{ Hooks.Register.ProcessFileContentBeforeSend(func (reader io.Reader, ctx *App, res *http.ResponseWriter, req *http.Request) (io.Reader, error){
{ if plugin_enable == false {
Type: PROCESS_FILE_CONTENT_BEFORE_SEND, // where to hook our plugin in the request lifecycle return reader, nil
Call: hook, // actual function we trigger }
Priority: -1, // last plugin to execute
},
}
}
func hook(reader io.Reader, ctx *App, res *http.ResponseWriter, req *http.Request) (io.Reader, error){ query := req.URL.Query()
if ctx.Config.Get("plugins.transcoder.image.enable").Bool() == false { mType := GetMimeType(query.Get("path"))
return reader, nil
}
Log.Debug("Plugin::Image")
query := req.URL.Query() if strings.HasPrefix(mType, "image/") == false {
mType := GetMimeType(query.Get("path")) 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 // Specify transformation
} else if mType == "image/svg" { transform := &lib.Transform{
return reader, nil Temporary: GetAbsolutePath(ImageCachePath + "image_" + QuickString(10)),
} else if mType == "image/x-icon" { Size: 300,
return reader, nil Crop: true,
} else if query.Get("thumbnail") != "true" && query.Get("size") == "" { Quality: 50,
return reader, nil 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 // Insert file in the fs
transform := &lib.Transform{ // => lower RAM usage while processing
Temporary: GetAbsolutePath(ImageCachePath + "image_" + QuickString(10)), file, err := os.OpenFile(transform.Temporary, os.O_WRONLY|os.O_CREATE, os.ModePerm)
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 { 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 reader, nil
} }
transform.Size = int(size) return lib.CreateThumbnail(transform)
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)
} }

View File

@ -1,24 +1,17 @@
package plugin package plugin
import ( import (
. "github.com/mickael-kerjean/nuage/server/common"
"os" "os"
"path/filepath" "path/filepath"
plg "plugin" plg "plugin"
. "github.com/mickael-kerjean/nuage/server/common"
"sort"
"strings" "strings"
"net/http"
"io"
) )
const PluginPath = "data/plugin/"
var plugins = make(map[string][]Plugin)
func init() { func init() {
ex, _ := os.Executable() ex, _ := os.Executable()
pPath := filepath.Join(filepath.Dir(ex), PluginPath) pPath := filepath.Join(filepath.Dir(ex), PLUGIN_PATH)
file, err := os.Open(pPath) file, err := os.Open(pPath)
if err != nil { if err != nil {
return return
@ -28,7 +21,7 @@ func init() {
c := NewConfig() c := NewConfig()
for i:=0; i < len(files); i++ { for i:=0; i < len(files); i++ {
name := files[i].Name() name := files[i].Name()
if strings.HasPrefix(name, ".") == true { if strings.HasPrefix(name, ".") {
continue continue
} }
p, err := plg.Open(pPath + "/" + name) p, err := plg.Open(pPath + "/" + name)
@ -36,32 +29,13 @@ func init() {
Log.Warning("Can't load plugin: %s => %v", name, err) Log.Warning("Can't load plugin: %s => %v", name, err)
continue continue
} }
fn, err := p.Lookup("Init")
f, err := p.Lookup("Register")
if err != nil { if err != nil {
Log.Warning("Can't register plugin: %s => %v", name, err) Log.Warning("Can't register plugin: %s => %v", name, err)
continue continue
} }
if obj, ok := f.(func(config *Config) []Plugin); ok { if obj, ok := fn.(func(config *Config)); ok {
for _, plg := range obj(c) { 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
})
}
} }
} }
} }
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
}